public static function fromDirectory(string $directory): array
    {
        if (!\is_dir($directory)) {
            throw Exception\DirectoryDoesNotExist::fromDirectory($directory);
        }

        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
            $directory,
            \RecursiveDirectoryIterator::FOLLOW_SYMLINKS
        ));

        $constructs = [];

        foreach ($iterator as $fileInfo) {
            /** @var \SplFileInfo $fileInfo */
            if (!$fileInfo->isFile()) {
                continue;
            }

            if ($fileInfo->getBasename('.php') === $fileInfo->getBasename()) {
                continue;
            }

            $fileName = $fileInfo->getRealPath();

            /** @var string $source */
            $source = \file_get_contents($fileName);

            try {
                $constructsFromFile = self::fromSource($source);
            } catch (Exception\ParseError $exception) {
                throw Exception\ParseError::fromFileNameAndParseError(
                    $fileName,
                    $exception
                );
            }

            if (0 === \count($constructsFromFile)) {
                continue;
            }

            foreach ($constructsFromFile as $construct) {
                $name = $construct->name();

                if (\array_key_exists($name, $constructs)) {
                    $construct = $constructs[$name];
                }

                $constructs[$name] = $construct->definedIn($fileName);
            }
        }

        \usort($constructs, static function (Construct $a, Construct $b) {
            return \strcmp(
                $a->name(),
                $b->name()
            );
        });

        $constructsWithMultipleDefinitions = \array_filter($constructs, static function (Construct $construct) {
            return 1 < \count($construct->fileNames());
        });

        if (0 < \count($constructsWithMultipleDefinitions)) {
            throw Exception\MultipleDefinitionsFound::fromConstructs($constructsWithMultipleDefinitions);
        }

        return \array_values($constructs);
    }