Leadtech/BootFramework

View on GitHub
src/Leadtech/Boot/Console/CompilerPass/ConsoleCompilerPass.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

namespace Boot\Console\CompilerPass;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use RuntimeException;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

/**
 * Class CommandCompilerPass.
 *
 * Automatically register tagged console commands to the console service.
 *
 * @author  Daan Biesterbos <daan@leadtech.nl>
 */
class ConsoleCompilerPass implements CompilerPassInterface
{
    /** @var  string */
    private $serviceIdentifier;

    /** @var string  */
    private $appName;

    /** @var string  */
    private $appVersion;

    /**
     * CommandCompilerPass constructor.
     *
     * @param string $serviceIdentifier The console service identifier, by default 'console'
     * @param string $appName           The application name, by default 'UNKNOWN'
     * @param string $appVersion        A version number, by default 'UNKNOWN'
     */
    public function __construct($serviceIdentifier, $appName, $appVersion)
    {
        $this->serviceIdentifier = $serviceIdentifier;
        $this->appName = $appName;
        $this->appVersion = $appVersion;
    }

    /**
     * @source 7 10  Look for tagged commands and add commands to console.
     *
     * @param ContainerBuilder $container Symfony2 container builder
     *
     * @throws RuntimeException Thrown while processing a missing or invalid console command
     */
    public function process(ContainerBuilder $container)
    {
        // Create or update console service definition
        $this->createOrUpdateServiceDefinition($container);

        // Get the console and verify type
        $console = $this->verifyAndGetConsoleService($container, $this->serviceIdentifier);

        // Find services with the 'console_command' tag
        $taggedServices = $container->findTaggedServiceIds('console_command');

        // Iterate the console commands and add them to the console component
        foreach ($taggedServices as $commandServiceId => $attributes) {

            // Get command
            $command = $container->get($commandServiceId);

            // Add the command to the console instance
            $console->add($command);

            // Add the definition to add the commands in the compiled service container as well.
            if ($container->hasDefinition($this->serviceIdentifier)) {
                $definition = $container->findDefinition($this->serviceIdentifier);
                $definition->addMethodCall('add', [new Reference($commandServiceId)]);
            }
        }
    }

    /**
     * @param ContainerBuilder $container
     */
    private function createOrUpdateServiceDefinition(ContainerBuilder $container)
    {
        // First check if the console service is already registered.
        // This code will sure to give preference to user registered services.
        if ($container->hasDefinition($this->serviceIdentifier)) {
            $consoleDefinition = $container->getDefinition($this->serviceIdentifier);

            // Set name only if not set already
            $methodDefinitions = [];
            foreach ($consoleDefinition->getMethodCalls() as $methodCall) {
                $methodDefinitions[$methodCall[0]] = $methodCall[1];
            }

            // Add definition for setName() if needed
            if (!isset($methodDefinitions['setName']) || $methodDefinitions['setName'][0] == 'UNKNOWN') {
                $consoleDefinition->addMethodCall('setName', [$this->appName]);
            }

            // Set version only if not set already
            if (!isset($methodDefinitions['setVersion']) || $methodDefinitions['setVersion'][0] == 'UNKNOWN') {
                $consoleDefinition->addMethodCall('setVersion', [$this->appVersion]);
            }
        } else {
            // Create new definition
            $consoleDefinition = new Definition(Application::class);
            $consoleDefinition->addMethodCall('setName', [$this->appName]);
            $consoleDefinition->addMethodCall('setVersion', [$this->appVersion]);
            $container->addDefinitions([
                $this->serviceIdentifier => $consoleDefinition,
            ]);

            // Update the current service instance, or the service from the compiled service container would be
            // different from the uncached version.
            $console = $container->get($this->serviceIdentifier);
            if (empty($console->getName()) || $console->getName() == 'UNKNOWN') {
                $console->setName($this->appName);
            }
            if (empty($console->getVersion()) || $console->getVersion() == 'UNKNOWN') {
                $console->setVersion($this->appVersion);
            }
        }
    }

    /**
     * @param ContainerBuilder $container
     * @param $serviceId
     *
     * @return object
     *
     * @throws \Throwable
     */
    private function verifyAndGetConsoleService(ContainerBuilder $container, $serviceId)
    {
        $console = $container->get($serviceId);
        if (!$console instanceof Application) {
            throw new \LogicException(
                'Program logic flow exception! '.
                "The service id {$serviceId} is reserved for an instance of ".Application::class
            );
        }

        return $console;
    }
}