diff --git a/src/DependencyInjection/Compiler/GatewayPass.php b/src/DependencyInjection/Compiler/GatewayPass.php old mode 100644 new mode 100755 index d92e510..3ec53c8 --- a/src/DependencyInjection/Compiler/GatewayPass.php +++ b/src/DependencyInjection/Compiler/GatewayPass.php @@ -8,12 +8,138 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; class GatewayPass implements CompilerPassInterface { + public const GATEWAYS_DIR = 'gateways'; + public const GATEWAY_NAMES_LOCK = 'gateway_names_compiled.lock'; + + public static function getGatewayCompileDir(): string + { + return sprintf( + '%s%svar%s%s', + \dirname(__DIR__, 3), + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + self::GATEWAYS_DIR, + ); + } + + public static function getGatewayNamesLockFile(): string + { + return sprintf( + '%s%s%s', + self::getGatewayCompileDir(), + DIRECTORY_SEPARATOR, + self::GATEWAY_NAMES_LOCK + ); + } + + /** + * Generates a directory for the gateways in the project var dir. + */ + public static function makeCompileDir() + { + $compileDir = self::getGatewayCompileDir(); + + if (!\is_dir($compileDir)) { + \mkdir($compileDir, 0777, true); + } + } + + /** + * Stores the gateway names in disk. + * + * @param array $names The names returned by the interfaces + */ + public static function compileGatewayNames(array $names) + { + self::makeCompileDir(); + + \file_put_contents( + self::getGatewayNamesLockFile(), + implode(PHP_EOL, $names) + ); + } + + private function getGatewayClasses(string $kernelDir): array + { + $classes = []; + + $namespacePieces = \array_slice(explode('\\', GatewayLocator::class), 0, -1); + + $economyDir = join(DIRECTORY_SEPARATOR, [ + $kernelDir, + 'src', + ...\array_slice($namespacePieces, 1) + ]); + + $economyDirPaths = \scandir($economyDir); + foreach ($economyDirPaths as $path) { + if ($path === "." || $path === "..") { + continue; + } + + $className = \rtrim($path, '.php'); + if (!\str_ends_with($className, 'Gateway')) { + continue; + } + + $reflection = new \ReflectionClass(sprintf("%s\%s", join("\\", $namespacePieces), $className)); + if ($reflection->isAbstract()) { + continue; + } + + $classes[] = $reflection->getName(); + } + + return $classes; + } + + /** + * @throws \Exception If there are two different Gateway classes with the same name + * + * @param array $gatewayClasses Fully-qualified Gateway class names + * @see GatewayInterface::getName() + */ + private function validateGatewayNames(array $gatewayClasses) + { + $gatewaysValidated = []; + foreach ($gatewayClasses as $gatewayClass) { + $gatewayName = $gatewayClass::getName(); + + if (\array_key_exists($gatewayName, $gatewaysValidated)) { + $exceptionMessage = sprintf( + "Duplicate Gateway name '%s' from class %s, name is already in use by class %s", + $gatewayName, + $gatewayClass, + $gatewaysValidated[$gatewayName] + ); + + throw new \Exception($exceptionMessage); + } + + $gatewaysValidated[$gatewayName] = $gatewayClass; + } + } + + /** + * @param array $gatewayClasses Fully-qualified Gateway class names + * @return array The names returned by each interface + */ + private function getGatewayNames(array $gatewayClasses): array + { + $names = []; + foreach ($gatewayClasses as $class) { + $names[] = $class::getName(); + } + + return $names; + } + public function process(ContainerBuilder $container): void { - /** @var GatewayLocator */ - $gatewayLocator = $container->get(GatewayLocator::class); + $gatewayClasses = $this->getGatewayClasses($container->getParameter('kernel.project_dir')); + $this->validateGatewayNames($gatewayClasses); - $gatewayLocator->validateGatewayNames(); - $gatewayLocator->compileGatewayNames(); + $gatewayNames = $this->getGatewayNames($gatewayClasses); + self::compileGatewayNames($gatewayNames); } }