samsonos/php_core

View on GitHub
src/loader/CoreLoader.php

Summary

Maintainability
C
1 day
Test Coverage
<?php declare(strict_types=1);

namespace samsonphp\core\loader;

use Doctrine\Common\Annotations\AnnotationReader;
use samson\activerecord\dbMySQLConnector;
use samson\core\CompressableExternalModule;
use samson\core\CompressableService;
use samson\core\Core;
use samson\core\ExternalModule;
use samson\core\VirtualModule;
use samsonframework\container\definition\analyzer\annotation\annotation\InjectClass;
use samsonframework\container\definition\analyzer\annotation\annotation\InjectParameter;
use samsonframework\container\definition\analyzer\annotation\annotation\InjectService;
use samsonframework\container\definition\analyzer\annotation\annotation\Service;
use samsonframework\container\definition\analyzer\annotation\AnnotationClassAnalyzer;
use samsonframework\container\definition\analyzer\annotation\AnnotationMethodAnalyzer;
use samsonframework\container\definition\analyzer\annotation\AnnotationPropertyAnalyzer;
use samsonframework\container\definition\analyzer\DefinitionAnalyzer;
use samsonframework\container\definition\analyzer\reflection\ReflectionClassAnalyzer;
use samsonframework\container\definition\analyzer\reflection\ReflectionMethodAnalyzer;
use samsonframework\container\definition\analyzer\reflection\ReflectionParameterAnalyzer;
use samsonframework\container\definition\analyzer\reflection\ReflectionPropertyAnalyzer;
use samsonframework\container\definition\builder\DefinitionBuilder;
use samsonframework\container\definition\builder\DefinitionCompiler;
use samsonframework\container\definition\builder\DefinitionGenerator;
use samsonframework\container\definition\ClassDefinition;
use samsonframework\container\definition\exception\MethodDefinitionAlreadyExistsException;
use samsonframework\container\definition\parameter\ParameterBuilder;
use samsonframework\container\definition\reference\ClassReference;
use samsonframework\container\definition\reference\ServiceReference;
use samsonframework\container\definition\reference\StringReference;
use samsonframework\container\definition\resolver\xml\XmlResolver;
use samsonframework\container\definition\scope\ModuleScope;
use samsonframework\container\definition\scope\ServiceScope;
use samsonframework\core\PreparableInterface;
use samsonframework\di\Container;
use samsonframework\generator\ClassGenerator;
use samsonframework\resource\ResourceMap;
use samsonphp\core\loader\module\Module;
use samsonphp\core\loader\module\ModuleManagerInterface;
use samsonphp\event\Event;
use samsonphp\i18n\i18n;

/**
 * Class CoreLoader
 *
 * @package samsonphp\container\loader
 */
class CoreLoader
{
    /** @var ModuleManagerInterface Module manager */
    protected $moduleManager;
    /** @var ContainerManager Container manager */
    public $containerManager;

    /**
     * CoreLoader constructor.
     *
     * @param ModuleManagerInterface $moduleManager
     * @param ContainerManager $containerManager
     */
    public function __construct(
        ModuleManagerInterface $moduleManager,
        ContainerManager $containerManager
    ) {
        $this->moduleManager = $moduleManager;
        $this->containerManager = $containerManager;
    }

    /**
     * Load modules
     *
     * @throws \Exception
     */
    public function init()
    {
        $containerPath = __DIR__ . '/../../../../../www/cache';
        $containerName = 'ContainerCore';
        $containerNamespace = 'samsonphp\core\loader';
        /** @var Module $module */
        $modules = $this->moduleManager->getRegisteredModules();

        $localModulesPath = '../src';
        ResourceMap::get('cache');
        $resourceMap = ResourceMap::get($localModulesPath);
        $localModules = $resourceMap->modules;

        if (false || !file_exists($containerPath . '/' . $containerName . '.php')) {

            $builder = new DefinitionBuilder(new ParameterBuilder());
            $xmlResolver = new XmlResolver();
            $xmlResolver->resolveFile($builder, __DIR__ . '/../../../../../app/config/config.xml');

            new Service('');
            new InjectService('');
            new InjectClass('');
            new InjectParameter('');

            foreach ($modules as $module) {
                if ($module->className && !$builder->hasDefinition($module->className)) {
                    // Fix samson.php files
                    if (!class_exists($module->className)) {
                        require_once($module->pathName);
                    }
                    /** @var ClassDefinition $classDefinition */
                    $classDefinition = $builder->addDefinition($module->className);
                    if ($id = $this->getModuleId($module->pathName)) {
                        $classDefinition->setServiceName($id);
                    } else {
                        // Generate identifier from module class
                        $classDefinition->setServiceName(
                            strtolower(ltrim(str_replace(__NS_SEPARATOR__, '_', $module->className), '_'))
                        );
                    }
                    $classDefinition->addScope(new ModuleScope())->setIsSingleton(true);
                    $this->defineConstructor($classDefinition, $module->path);
                }
            }

            $classDefinition = $builder->addDefinition(VirtualModule::class);
            $classDefinition->addScope(new ModuleScope())->setServiceName('local')->setIsSingleton(true);
            $this->defineConstructor($classDefinition, getcwd());

            foreach ($localModules as $moduleFile) {
                if (!$builder->hasDefinition($moduleFile[0])) {

                    /** @var ClassDefinition $classDefinition */
                    $classDefinition = $builder->addDefinition($moduleFile[0]);
                    $classDefinition->addScope(new ModuleScope());
                    $classDefinition->setIsSingleton(true);
                    if ($id = $this->getModuleId($moduleFile[1])) {
                        $classDefinition->setServiceName($id);
                    } else {
                        throw new \Exception('Can not get id of local module');
                    }

                    $modulePath = explode('/', str_replace(realpath($localModulesPath), '', $moduleFile[1]));
                    $this->defineConstructor($classDefinition, $localModulesPath . '/' . $modulePath[1]);
                }
            }


            /**
             * Add implementors
             */
            foreach ($this->moduleManager->implements as $interfaceName => $class) {
                $builder->defineImplementors($interfaceName, new ClassReference($class));
            }

            // Init compiler
            $reader = new AnnotationReader();
            $compiler = new DefinitionCompiler(
                new DefinitionGenerator(new ClassGenerator()),
                (new DefinitionAnalyzer())
                    ->addClassAnalyzer(new AnnotationClassAnalyzer($reader))
                    ->addClassAnalyzer(new ReflectionClassAnalyzer())
                    ->addMethodAnalyzer(new AnnotationMethodAnalyzer($reader))
                    ->addMethodAnalyzer(new ReflectionMethodAnalyzer())
                    ->addPropertyAnalyzer(new AnnotationPropertyAnalyzer($reader))
                    ->addPropertyAnalyzer(new ReflectionPropertyAnalyzer())
                    ->addParameterAnalyzer(new ReflectionParameterAnalyzer())
            );

            $container = $compiler->compile($builder, $containerName, $containerNamespace, $containerPath);

        } else {

            $containerClassName = $containerNamespace. '\\' . $containerName;
            require_once($containerPath . '/' . $containerName . '.php');
            $container = new $containerClassName();
        }

        $GLOBALS['__core'] = $container->get('core');

        $this->prepareModules($modules, $container);

        /** @var array $module */
        foreach ($localModules as $module) {
            $instance = $container->get($module[0]);
            $instance->parent = $this->getClassParentModule($container, get_parent_class($instance));
        }

//        $container->get('core')->active($container->get('local'));

        return $container;
    }

    /**
     * Prepare modules
     *
     * @param array $modules
     * @param $container
     */
    protected function prepareModules(array $modules, $container)
    {
        foreach ($modules as $module) {
            $identifier = $module->name;
            if ($module->className) {
                // Fix samson.php files
                if (!class_exists($module->className)) {
                    require_once($module->pathName);
                }
                $instance = $container->get($module->className);
            } else {
                continue;
            }

            // Set composer parameters
            $instance->composerParameters = $module->composerParameters;

            // TODO: Change event signature to single approach
            // Fire core module load event
            Event::fire('core.module_loaded', [$identifier, &$instance]);

            // Signal core module configure event
            Event::signal('core.module.configure', [&$instance, $identifier]);

            if ($instance instanceof PreparableInterface) {
                // Call module preparation handler
                if (!$instance->prepare()) {
//                    throw new \Exception($identifier.' - Module preparation stage failed');
                }
            }

            $instance->parent = $this->getClassParentModule($container, get_parent_class($instance));
        }
    }

    /**
     * Define constructor
     *
     * @param ClassDefinition $classDefinition
     * @param $path
     * @throws MethodDefinitionAlreadyExistsException
     */
    protected function defineConstructor(ClassDefinition $classDefinition, $path)
    {
        $classDefinition->defineConstructor()
            ->defineParameter('path')
                ->defineDependency(new StringReference($path))
            ->end()
        ->end();
    }

    /**
     * Get module id
     *
     * @param $filePath
     * @return string|bool
     */
    protected function getModuleId($filePath)
    {
        preg_match(
            '/.*(protected|public|private)\s\$id\s=\s(\'|\")(?P<id>.*)(\'|\")\;.*/',
            file_get_contents($filePath),
            $matches
        );

        if (array_key_exists('id', $matches) && isset($matches['id']{0})) {
            return $matches['id'];
        } else {
            return false;
        }
    }

    /**
     * Find parent module by OOP class inheritance.
     *
     * @param string $className Class name for searching parent modules
     * @param array  $ignoredClasses Collection of ignored classes
     *
     * @return null|mixed Parent service instance if present
     */
    protected function getClassParentModule(
        Container $container,
        $className,
        array $ignoredClasses = [
            ExternalModule::class,
            CompressableExternalModule::class,
            \samson\core\Service::class,
            CompressableService::class
        ]
    ) {
        // Skip ignored class names
        if (!in_array($className, $ignoredClasses, true)) {
            try {
                $instance = $container->get(trim($className));
                $instance->parent = $this->getClassParentModule($container, get_parent_class($instance));
                return $instance;
            } catch (\Exception $exception) {
                return null;
            }
//            // Iterate loaded services
//            foreach ($container->getServices('module') as $service) {
//                if (get_class($service) === $className) {
//                    return $service;
//                }
//            }
        }
        return null;
    }
}