Leadtech/BootFramework

View on GitHub
src/Leadtech/Boot/ApplicationContext.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace Boot;

use Boot\Utils\StringUtils;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\ExpressionLanguageProvider;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Finder\SplFileInfo;

/**
 * Class ApplicationContext.
 *
 * @author  Daan Biesterbos <daan@leadtech.nl>
 */
class ApplicationContext
{
    /** @var  ContainerInterface|ContainerBuilder */
    protected $serviceContainer;

    /** @var  string */
    protected $environment;

    /** @var  string */
    protected $compiledClassDir;

    /** @var array  */
    protected $directories = [];

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

    /** @var array  */
    protected $compilerPasses = [];

    /** @var  string */
    protected $projectDir;

    /**
     * @param string $appName
     * @param string $environment
     */
    public function __construct($appName = 'default', $environment = Boot::PRODUCTION)
    {
        $this->appName = $appName;
        $this->environment = $environment;
    }

    /**
     * @param array                        $params        Parameters to register to the service container
     * @param bool                         $isOptimized   Whether application is optimized (pre-compiled/optimized obj)
     * @param InitializerInterface[]       $initializers  A number of initializers (-/ bootstrap components)
     * @param ExpressionLanguageProvider[] $providers     A number of expression language providers
     *
     * @return ContainerInterface
     */
    public function bootstrap(array $params = [], $isOptimized = true, array $initializers = [], array $providers = [])
    {
        // Load the services if optimization is not enabled.
        if (!$isOptimized) {
            $this->createServiceContainer($params, $initializers, $providers);
        } else {
            $this->getOrCreateOptimizedServiceContainer($params, $initializers, $providers);
        }

        return $this->getServiceContainer();
    }

    /**
     * @param array                        $params        Parameters to register to the service container
     * @param InitializerInterface[]       $initializers  A number of initializers (-/ bootstrap components)
     * @param ExpressionLanguageProvider[] $providers     A number of expression language providers
     *
     * @return ContainerInterface
     */
    protected function getOrCreateOptimizedServiceContainer(array $params = [], array $initializers, array $providers)
    {
        // Get compiled context class
        $compiledClass = $this->getCompiledClassName();

        // Determine path to compiled service container
        $classPath = $this->getCompiledClassDir().DIRECTORY_SEPARATOR.$compiledClass.'.php';

        // Create cache object
        $configCache = new ConfigCache($classPath, $this->environment != Boot::PRODUCTION);

        // Load the services if the cached version is dirty
        if (!$configCache->isFresh()) {

            // Create new container
            $this->createServiceContainer($params, $initializers, $providers);

            // Generates optimized class reflecting the final state of the service container.
            $dumper = new PhpDumper($this->serviceContainer);
            $configCache->write(
                $dumper->dump(['class' => $compiledClass]),
                $this->serviceContainer->getResources()
            );

        } else {

            // Load the compiled class
            require_once $classPath;

            // Create instance
            $this->serviceContainer = new $compiledClass();

            // Bootstrap initializer(s)
            foreach ($initializers as $initializer) {
                $initializer->bootstrap($this->serviceContainer);
            }

        }
    }

    /**
     * @param array                        $parameters    Parameters to register to the service container. Converted to uppercase snake case format.
     * @param InitializerInterface[]       $initializers
     * @param ExpressionLanguageProvider[] $exprProviders
     *
     * @return ContainerBuilder
     */
    protected function createServiceContainer(array $parameters = null, array $initializers = [], array $exprProviders = null)
    {
        // Build container
        $this->serviceContainer = new ContainerBuilder();

        // Load all components
        $this
            ->loadExpressionProviders($exprProviders)
            ->loadInitializers($initializers)
            ->loadCompilerPasses($this->getCompilerPasses())
            ->loadParameters($parameters)
            ->loadConfigurations($this->getDirectories())
        ;

        $this->serviceContainer->compile();

        return $this->serviceContainer;
    }

    /**
     * @param ExpressionLanguageProvider[]|null $providers
     *
     * @return $this
     */
    private function loadExpressionProviders($providers)
    {
        if (is_array($providers)) {
            // Expression providers
            //$this->serviceContainer->addExpressionLanguageProvider(new ExpressionLanguageProvider());
            foreach ($providers as $provider) {
                $this->serviceContainer->addExpressionLanguageProvider($provider);
            }
        }

        return $this;
    }

    /**
     * @param InitializerInterface[]|null $initializers
     * @return $this
     */
    private function loadInitializers($initializers)
    {
        // Load initializers
        if (is_array($initializers)) {
            foreach ($initializers as $initializer) {
                $initializer->bootstrap($this->serviceContainer);
            }
        }

        return $this;
    }

    /**
     * @param array|null $compilerPasses
     *
     * @return $this
     */
    private function loadCompilerPasses($compilerPasses)
    {
        // Load compiler passes
        if (is_array($compilerPasses)) {
            foreach ($compilerPasses as list($pass, $type)) {
                if ($pass instanceof CompilerPassInterface) {
                    $this->serviceContainer->addCompilerPass($pass, $type);
                }
            }
        }

        return $this;
    }

    /**
     * @param array|null $parameters
     *
     * @return $this
     */
    private function loadParameters($parameters)
    {
        // Note that this must be done before loading the context files.
        // It should be possible to override the parameters from the service container.
        if (is_array($parameters)) {
            foreach ($parameters as $name => $value) {
                $param = preg_replace('/[^a-z0-9_.:@]+/', '', $name);
                $this->serviceContainer->setParameter($param, $value);
            }
        }

        return $this;
    }

    /**
     * @param string[]|null $directories
     *
     * @return $this
     */
    public function loadConfigurations($directories)
    {
        // Load configurations
        if (is_array($directories)) {
            foreach ($directories as $directory) {
                if (is_dir($directory)) {
                    $this->loadConfiguration($directory);
                }
            }
        }

        return $this;
    }


    /**
     * Load configuration from module resource folder.
     *
     * @param string $resourceDir
     */
    public function loadConfiguration($resourceDir)
    {
        // Create xml loader
        $loader = new XmlFileLoader($this->serviceContainer, new FileLocator($resourceDir));

        // Load parameters.xml first to ensure the parameters are available
        $filepath = realpath($resourceDir).'/parameters.xml';
        if (file_exists($filepath)) {
            $loader->load($filepath);
        }

        // Load configurations from current directory
        foreach (new \DirectoryIterator($resourceDir) as $file) {
            if ($file->getFilename() != 'parameters.xml') {
                if ($file->isFile() && strtolower($file->getExtension()) == 'xml') {
                    $loader->load($file->getFilename());
                }
            }
        }

        // Check if there are environment configurations available
        // Look for environment settings e.g. `prod`, `dev`, or `test`.
        $targetDirEnv = $resourceDir.DIRECTORY_SEPARATOR.$this->getEnvironment();
        if (is_dir($targetDirEnv)) {
            $this->loadConfiguration($targetDirEnv);
        }
    }

    /**
     * @return string prod|dev|test
     */
    public function getEnvironment()
    {
        return $this->environment;
    }

    /**
     * @param $environment
     *
     * @return $this
     */
    public function setEnvironment($environment)
    {
        $this->environment = $environment;

        return $this;
    }

    /**
     * @throws \Exception
     *
     * @return string
     */
    public function getCompiledClassDir()
    {
        return $this->compiledClassDir;
    }

    /**
     * @param string $compiledClassDir
     *
     * @return $this
     */
    public function setCompiledClassDir($compiledClassDir)
    {
        $this->compiledClassDir = $compiledClassDir;

        return $this;
    }

    /**
     * @return array
     */
    public function getDirectories()
    {
        return $this->directories;
    }

    /**
     * @param array $directories
     *
     * @return $this
     */
    public function setDirectories($directories)
    {
        $this->directories = $directories;

        return $this;
    }

    /**
     * @return array
     */
    public function getCompilerPasses()
    {
        return $this->compilerPasses;
    }

    /**
     * @param array $compilerPasses [compilerPass,type][]
     *
     * @return $this
     */
    public function setCompilerPasses(array $compilerPasses)
    {
        $this->compilerPasses = $compilerPasses;

        return $this;
    }

    /**
     * @return string
     */
    public function getCompiledClassName()
    {
        return strtr('Compiled{app}{env}', [
            '{app}' => StringUtils::camelCase(ucfirst($this->appName ?: 'default')),
            '{env}' => StringUtils::camelCase(strtolower($this->environment) ?: 'prod'),
        ]);
    }

    /**
     * @return ContainerInterface
     */
    public function getServiceContainer()
    {
        return $this->serviceContainer;
    }
}