edde-framework/edde-framework

View on GitHub
src/Edde/Ext/Container/ContainerFactory.php

Summary

Maintainability
D
2 days
Test Coverage
<?php
    declare(strict_types=1);

    namespace Edde\Ext\Container;

    use Edde\Api\Acl\IAcl;
    use Edde\Api\Acl\IAclManager;
    use Edde\Api\Application\IApplication;
    use Edde\Api\Application\IContext;
    use Edde\Api\Application\IResponseManager;
    use Edde\Api\Asset\IAssetDirectory;
    use Edde\Api\Asset\IAssetStorage;
    use Edde\Api\Asset\IStorageDirectory;
    use Edde\Api\Cache\ICache;
    use Edde\Api\Cache\ICacheDirectory;
    use Edde\Api\Cache\ICacheManager;
    use Edde\Api\Cache\ICacheStorage;
    use Edde\Api\Container\ContainerException;
    use Edde\Api\Container\FactoryException;
    use Edde\Api\Container\IContainer;
    use Edde\Api\Container\IFactory;
    use Edde\Api\Converter\IConverterManager;
    use Edde\Api\Crate\ICrate;
    use Edde\Api\Crate\ICrateFactory;
    use Edde\Api\Crypt\ICryptEngine;
    use Edde\Api\Database\IDriver;
    use Edde\Api\Database\IDsn;
    use Edde\Api\EddeException;
    use Edde\Api\File\IRootDirectory;
    use Edde\Api\File\ITempDirectory;
    use Edde\Api\Html\IHtmlGenerator;
    use Edde\Api\Http\Client\IHttpClient;
    use Edde\Api\Http\IHostUrl;
    use Edde\Api\Http\IHttpRequest;
    use Edde\Api\Http\IHttpResponse;
    use Edde\Api\Identity\IAuthenticatorManager;
    use Edde\Api\Identity\IIdentity;
    use Edde\Api\Identity\IIdentityManager;
    use Edde\Api\Job\IJobManager;
    use Edde\Api\Job\IJobQueue;
    use Edde\Api\Link\ILinkFactory;
    use Edde\Api\Lock\ILockDirectory;
    use Edde\Api\Lock\ILockManager;
    use Edde\Api\Log\ILogDirectory;
    use Edde\Api\Log\ILogService;
    use Edde\Api\Protocol\Event\IEventBus;
    use Edde\Api\Protocol\IElementStore;
    use Edde\Api\Protocol\IProtocolManager;
    use Edde\Api\Protocol\IProtocolService;
    use Edde\Api\Protocol\Request\IRequestService;
    use Edde\Api\Resource\IResourceManager;
    use Edde\Api\Resource\IResourceProvider;
    use Edde\Api\Router\IRouterService;
    use Edde\Api\Runtime\IRuntime;
    use Edde\Api\Schema\ISchemaManager;
    use Edde\Api\Session\IFingerprint;
    use Edde\Api\Session\ISessionDirectory;
    use Edde\Api\Session\ISessionManager;
    use Edde\Api\Storage\IStorage;
    use Edde\Api\Store\IStore;
    use Edde\Api\Store\IStoreDirectory;
    use Edde\Api\Store\IStoreManager;
    use Edde\Api\Template\ICompiler;
    use Edde\Api\Template\ITemplate;
    use Edde\Api\Template\ITemplateDirectory;
    use Edde\Api\Template\ITemplateManager;
    use Edde\Api\Thread\IExecutor;
    use Edde\Api\Thread\IThreadManager;
    use Edde\Api\Translator\ITranslator;
    use Edde\Api\Upgrade\IUpgradeManager;
    use Edde\Api\Web\IJavaScriptCompiler;
    use Edde\Api\Web\IStyleSheetCompiler;
    use Edde\Api\Xml\IXmlExport;
    use Edde\Api\Xml\IXmlParser;
    use Edde\Common\Acl\Acl;
    use Edde\Common\Acl\AclManager;
    use Edde\Common\Application\Application;
    use Edde\Common\Application\ResponseManager;
    use Edde\Common\Asset\AssetDirectory;
    use Edde\Common\Asset\AssetStorage;
    use Edde\Common\Asset\StorageDirectory;
    use Edde\Common\Cache\Cache;
    use Edde\Common\Cache\CacheDirectory;
    use Edde\Common\Cache\CacheManager;
    use Edde\Common\Container\Container;
    use Edde\Common\Container\Factory\CallbackFactory;
    use Edde\Common\Container\Factory\ClassFactory;
    use Edde\Common\Container\Factory\ExceptionFactory;
    use Edde\Common\Container\Factory\InstanceFactory;
    use Edde\Common\Container\Factory\InterfaceFactory;
    use Edde\Common\Container\Factory\LinkFactory;
    use Edde\Common\Container\Factory\ProxyFactory;
    use Edde\Common\Converter\ConverterManager;
    use Edde\Common\Crate\Crate;
    use Edde\Common\Crate\CrateFactory;
    use Edde\Common\Crypt\CryptEngine;
    use Edde\Common\Database\DatabaseStorage;
    use Edde\Common\File\RootDirectory;
    use Edde\Common\File\TempDirectory;
    use Edde\Common\Html\Html5Generator;
    use Edde\Common\Http\Client\HttpClient;
    use Edde\Common\Http\HostUrl;
    use Edde\Common\Http\HttpRequest;
    use Edde\Common\Http\HttpResponse;
    use Edde\Common\Identity\AuthenticatorManager;
    use Edde\Common\Identity\IdentityManager;
    use Edde\Common\Job\JobManager;
    use Edde\Common\Job\JobQueue;
    use Edde\Common\Lock\FileLockManager;
    use Edde\Common\Lock\LockDirectory;
    use Edde\Common\Log\LogDirectory;
    use Edde\Common\Log\LogService;
    use Edde\Common\Object;
    use Edde\Common\Protocol\ElementStore;
    use Edde\Common\Protocol\Event\EventBus;
    use Edde\Common\Protocol\ProtocolManager;
    use Edde\Common\Protocol\ProtocolService;
    use Edde\Common\Protocol\Request\RequestService;
    use Edde\Common\Resource\ResourceManager;
    use Edde\Common\Router\RouterService;
    use Edde\Common\Runtime\Runtime;
    use Edde\Common\Schema\SchemaManager;
    use Edde\Common\Session\SessionDirectory;
    use Edde\Common\Session\SessionFingerprint;
    use Edde\Common\Session\SessionManager;
    use Edde\Common\Store\StoreDirectory;
    use Edde\Common\Store\StoreManager;
    use Edde\Common\Template\Compiler;
    use Edde\Common\Template\Template;
    use Edde\Common\Template\TemplateDirectory;
    use Edde\Common\Template\TemplateManager;
    use Edde\Common\Thread\ThreadManager;
    use Edde\Common\Thread\WebExecutor;
    use Edde\Common\Translator\Translator;
    use Edde\Common\Upgrade\AbstractUpgradeManager;
    use Edde\Common\Web\JavaScriptCompiler;
    use Edde\Common\Web\StyleSheetCompiler;
    use Edde\Common\Xml\XmlExport;
    use Edde\Common\Xml\XmlParser;
    use Edde\Ext\Cache\FlatFileCacheStorage;
    use Edde\Ext\Cache\InMemoryCacheStorage;
    use Edde\Ext\Converter\ConverterManagerConfigurator;
    use Edde\Ext\Database\Sqlite\SqliteDriver;
    use Edde\Ext\Database\Sqlite\SqliteDsn;
    use Edde\Ext\Job\ThreadManagerConfigurator;
    use Edde\Ext\Link\LinkFactoryConfigurator;
    use Edde\Ext\Log\LogServiceConfigurator;
    use Edde\Ext\Protocol\ProtocolServiceConfigurator;
    use Edde\Ext\Protocol\RequestServiceConfigurator;
    use Edde\Ext\Resource\ResourceManagerConfigurator;
    use Edde\Ext\Router\RouterServiceConfigurator;
    use Edde\Ext\Store\StoreManagerConfigurator;
    use Edde\Ext\Template\CompilerConfigurator;
    use Edde\Ext\Thread\WebExecutorConfigurator;

    class ContainerFactory extends Object {
        /**
         * @param array $factoryList
         *
         * @return IFactory[]
         * @throws FactoryException
         */
        static public function createFactoryList(array $factoryList): array {
            $factories = [];
            foreach ($factoryList as $name => $factory) {
                $current = null;
                if ($factory instanceof \stdClass) {
                    switch ($factory->type) {
                        case 'instance':
                            $current = new InstanceFactory($name, $factory->class, $factory->parameterList, null, $factory->cloneable);
                            break;
                        case 'exception':
                            $current = new ExceptionFactory($name, $factory->class, $factory->message);
                            break;
                        case 'proxy':
                            $current = new ProxyFactory($name, $factory->factory, $factory->method, $factory->parameterList);
                            break;
                    }
                } else if (is_string($factory) && strpos($factory, '::') !== false) {
                    list($target, $method) = explode('::', $factory);
                    $reflectionMethod = new \ReflectionMethod($target, $method);
                    $current = new ProxyFactory($name, $target, $method);
                    if ($reflectionMethod->isStatic()) {
                        $current = new CallbackFactory($factory, $name);
                    }
                } else if (is_string($name) && is_string($factory) && (interface_exists($factory) || class_exists($factory))) {
                    if (class_exists($factory)) {
                        $current = new InterfaceFactory($name, $factory);
                    } else if (interface_exists($factory)) {
                        $current = new LinkFactory($name, $factory);
                    }
                } else if ($factory instanceof IFactory) {
                    $current = $factory;
                } else if (is_callable($factory)) {
                    throw new FactoryException(sprintf('Closure is not supported in factory definition [%s].', $name));
                }
                if ($current === null) {
                    throw new FactoryException(sprintf('Unsupported factory definition [%s; %s].', is_string($name) ? $name : (is_object($name) ? get_class($name) : gettype($name)), is_string($factory) ? $factory : (is_object($factory) ? get_class($factory) : gettype($factory))));
                }
                $factories[$name] = $current;
            }
            return $factories;
        }

        /**
         * pure way how to simple create a system container
         *
         * @param array    $factoryList
         * @param string[] $configuratorList
         *
         * @return IContainer
         * @throws ContainerException
         * @throws FactoryException
         */
        static public function create(array $factoryList = [], array $configuratorList = []): IContainer {
            /**
             * A young man and his date were parked on a back road some distance from town.
             * They were about to have sex when the girl stopped.
             * “I really should have mentioned this earlier, but I’m actually a hooker and I charge $20 for sex.”
             * The man reluctantly paid her, and they did their thing.
             * After a cigarette, the man just sat in the driver’s seat looking out the window.
             * “Why aren’t we going anywhere?” asked the girl.
             * “Well, I should have mentioned this before, but I’m actually a taxi driver, and the fare back to town is $25…”
             */
            /** @var $container IContainer */
            $container = new Container(new Cache(new InMemoryCacheStorage()));
            /**
             * this trick ensures that container is properly configured when some internal dependency needs it while container is construction
             */
            $containerConfigurator = $configuratorList[IContainer::class] = new ContainerConfigurator();
            $containerConfigurator->setFactoryList($factoryList = self::createFactoryList($factoryList));
            $containerConfigurator->setConfiguratorList($configuratorList);
            $container->addConfigurator($containerConfigurator);
            $container->setup();
            $container = $container->create(IContainer::class);
            $container->setup();
            return $container;
        }

        /**
         * create a default container with set of services from Edde; they can be simply redefined
         *
         * @param array    $factoryList
         * @param string[] $configuratorList
         *
         * @return IContainer
         * @throws ContainerException
         * @throws FactoryException
         */
        static public function container(array $factoryList = [], array $configuratorList = []): IContainer {
            return self::create(array_merge(self::getDefaultFactoryList(), $factoryList), array_filter(array_merge(self::getDefaultConfiguratorList(), $configuratorList)));
        }

        /**
         * this magical method tries to guess root directory based on a stack trace
         *
         * @param array $factoryList
         * @param array $configuratorList
         *
         * @return IContainer
         * @throws ContainerException
         * @throws FactoryException
         */
        static public function containerWithRoot(array $factoryList = [], array $configuratorList = []): IContainer {
            list(, $trace) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
            $factoryList[IRootDirectory::class] = $factoryList[IRootDirectory::class] ?? self::instance(RootDirectory::class, [dirname($trace['file'])]);
            return self::container($factoryList, $configuratorList);
        }

        /**
         * shortcut for autowiring (for example in tests, ...)
         *
         * @param mixed $instance
         * @param array $factoryList
         * @param array $configuratorList
         *
         * @return IContainer
         * @throws ContainerException
         * @throws FactoryException
         */
        static public function autowire($instance, array $factoryList = [], array $configuratorList = []): IContainer {
            $container = self::containerWithRoot(empty($factoryList) ? [new ClassFactory()] : $factoryList, $configuratorList);
            $container->autowire($instance);
            return $container;
        }

        /**
         * create container and serialize the result into the file; if file exists, container is build from it
         *
         * @param array  $factoryList
         * @param array  $configuratorList
         * @param string $cacheId
         *
         * @return IContainer
         * @throws ContainerException
         * @throws FactoryException
         */
        static public function cache(array $factoryList, array $configuratorList, string $cacheId): IContainer {
            if ($container = @file_get_contents($cacheId)) {
                /** @noinspection UnserializeExploitsInspection */
                return unserialize($container);
            }
            register_shutdown_function(function (IContainer $container, $cache) {
                file_put_contents($cache, serialize($container));
            }, $container = self::container($factoryList, $configuratorList), $cacheId);
            return $container;
        }

        /**
         * create instance factory
         *
         * @param string $class
         * @param array  $parameterList
         * @param bool   $cloneable
         *
         * @return object
         */
        static public function instance(string $class, array $parameterList, bool $cloneable = false) {
            return (object)[
                'type'          => __FUNCTION__,
                'class'         => $class,
                'parameterList' => $parameterList,
                'cloneable'     => $cloneable,
            ];
        }

        /**
         * special kind of factory which will thrown an exception of the given message; it's useful for say which internal dependencies are not met
         *
         * @param string $message
        |         * @param string|null $class
         *
         * @return object
         */
        static public function exception(string $message, string $class = null) {
            return (object)[
                'type'    => __FUNCTION__,
                'message' => $message,
                'class'   => $class ?: EddeException::class,
            ];
        }

        /**
         * create proxy call factory
         *
         * @param string $factory
         * @param string $method
         * @param array  $parameterList
         *
         * @return object
         */
        static public function proxy(string $factory, string $method, array $parameterList) {
            return (object)[
                'type'          => __FUNCTION__,
                'factory'       => $factory,
                'method'        => $method,
                'parameterList' => $parameterList,
            ];
        }

        static public function getDefaultFactoryList(): array {
            return [
                IContainer::class         => Container::class,
                IRootDirectory::class     => self::exception(sprintf('Root directory is not specified; please register [%s] interface.', IRootDirectory::class)),
                ITempDirectory::class     => self::proxy(IRootDirectory::class, 'directory', [
                    'temp',
                    TempDirectory::class,
                ]),
                IAssetDirectory::class    => self::proxy(IRootDirectory::class, 'directory', [
                    '.assets',
                    AssetDirectory::class,
                ]),
                ITemplateDirectory::class => self::proxy(IAssetDirectory::class, 'directory', [
                    'templates',
                    TemplateDirectory::class,
                ]),
                ILogDirectory::class      => self::proxy(IRootDirectory::class, 'directory', [
                    'logs',
                    LogDirectory::class,
                ]),

                IStorageDirectory::class => self::proxy(IAssetDirectory::class, 'directory', [
                    'storage',
                    StorageDirectory::class,
                ]),

                /**
                 * Happy cacheing stuff here :)
                 */
                ICacheManager::class     => CacheManager::class,
                ICache::class            => ICacheManager::class,
                ICacheStorage::class     => FlatFileCacheStorage::class,
                ICacheDirectory::class   => self::proxy(ITempDirectory::class, 'directory', [
                    'cache',
                    CacheDirectory::class,
                ]),

                IRuntime::class         => Runtime::class,

                /**
                 * Application request/response stuff
                 */
                IApplication::class     => Application::class,
                IContext::class         => self::exception(sprintf('You have to register implementation of [%s] specific for you application.', IContext::class)),
                IRouterService::class   => RouterService::class,
                IHttpRequest::class     => HttpRequest::class . '::createHttpRequest',
                IHttpResponse::class    => HttpResponse::class . '::createHttpResponse',
                IResponseManager::class => ResponseManager::class,

                IHostUrl::class                       => HostUrl::class . '::createHostUrl',

                /**
                 * Link generation
                 */
                ILinkFactory::class                   => \Edde\Common\Link\LinkFactory::class,

                /**
                 * Support for general content conversion (which also powers server content negotiation)
                 */
                IConverterManager::class              => ConverterManager::class,

                /**
                 * Resource related stuff
                 */
                IResourceManager::class               => ResourceManager::class,
                IResourceProvider::class              => IResourceManager::class,

                /**
                 * Web components related stuff
                 */
                IStyleSheetCompiler::class            => StyleSheetCompiler::class,
                IJavaScriptCompiler::class            => JavaScriptCompiler::class,

                /**
                 * Storage (database) related stuff
                 */
                IStorage::class                       => DatabaseStorage::class,
                IDriver::class                        => SqliteDriver::class,
                IDsn::class                           => self::instance(SqliteDsn::class, ['storage.sqlite']),

                /**
                 * General crate support
                 */
                ICrate::class                         => self::instance(Crate::class, [], true),
                ICrateFactory::class                  => CrateFactory::class,
                ISchemaManager::class                 => SchemaManager::class,

                /**
                 * Http client support
                 */
                IHttpClient::class                    => HttpClient::class,

                /**
                 * General log service
                 */
                ILogService::class                    => LogService::class,

                /**
                 * Custom simple XML parser
                 */
                IXmlParser::class                     => XmlParser::class,

                /**
                 * General support for html generator and template engine
                 */
                IHtmlGenerator::class                 => Html5Generator::class,
                ITemplateManager::class               => TemplateManager::class,
                ITemplate::class                      => self::instance(Template::class, [], true),
                ICompiler::class                      => Compiler::class,

                /**
                 * It's nice when it is possible to upgrade you application...
                 */
                IUpgradeManager::class                => self::exception(sprintf('Upgrade manager is not available; you must register [%s] interface; optionaly default [%s] implementation should help you.', IUpgradeManager::class, AbstractUpgradeManager::class)),

                /**
                 * Simple crypto layer
                 */
                ICryptEngine::class                   => CryptEngine::class,

                /**
                 * Access control, session and Identity (user session)
                 */
                IAclManager::class                    => AclManager::class,
                ISessionManager::class                => SessionManager::class,
                ISessionDirectory::class              => self::proxy(ITempDirectory::class, 'directory', [
                    'session',
                    SessionDirectory::class,
                ]),
                IIdentityManager::class               => IdentityManager::class,
                IIdentity::class                      => IIdentityManager::class,
                IFingerprint::class                   => SessionFingerprint::class,
                IAuthenticatorManager::class          => AuthenticatorManager::class,
                IAclManager::class                    => AclManager::class,
                IAcl::class                           => Acl::class,

                /**
                 * Translation support
                 */
                ITranslator::class                    => Translator::class,

                /**
                 * General asset storage support
                 */
                IAssetStorage::class                  => AssetStorage::class,

                /**
                 * Protocol implementation support
                 */
                IProtocolManager::class               => ProtocolManager::class,
                IProtocolService::class               => ProtocolService::class,
                IRequestService::class                => RequestService::class,
                IEventBus::class                      => EventBus::class,
                \Edde\Ext\Rest\ProtocolService::class => \Edde\Ext\Rest\ProtocolService::class,
                IElementStore::class                  => ElementStore::class,

                /**
                 * Job related implementation
                 */
                IJobManager::class                    => JobManager::class,
                IJobQueue::class                      => JobQueue::class,

                /**
                 * Thread support
                 */
                IThreadManager::class                 => ThreadManager::class,
                IExecutor::class                      => WebExecutor::class,

                /**
                 * Store related stuff
                 */
                IStoreManager::class                  => StoreManager::class,
                IStore::class                         => IStoreManager::class,
                IStoreDirectory::class                => self::proxy(IAssetDirectory::class, 'directory', [
                    'store',
                    StoreDirectory::class,
                ]),

                /**
                 * xml support
                 */
                IXmlExport::class                     => XmlExport::class,

                /**
                 * General Locking support
                 */
                ILockManager::class                   => FileLockManager::class,
                ILockDirectory::class                 => self::proxy(IAssetDirectory::class, 'directory', [
                    '.lock',
                    LockDirectory::class,
                ]),
            ];
        }

        static public function getDefaultConfiguratorList(): array {
            return [
                IRouterService::class    => RouterServiceConfigurator::class,
                /**
                 * We are using some custom resource providers, so we have to register them to resource manager and the current
                 * point how to get resources.
                 */
                IResourceManager::class  => ResourceManagerConfigurator::class,
                /**
                 * To enable general content exchange, we have to setup converter manager; it basically allows to do arbitrary
                 * data conversions for example json to array, xml file to INode, ... this component is kind of fundamental part
                 * of the framework.
                 */
                IConverterManager::class => ConverterManagerConfigurator::class,
                /**
                 * As other components, Template engine should be configured too; this will register default set of macros.
                 */
                ICompiler::class         => CompilerConfigurator::class,
                IProtocolService::class  => ProtocolServiceConfigurator::class,
                IRequestService::class   => RequestServiceConfigurator::class,
                ILogService::class       => LogServiceConfigurator::class,
                ILinkFactory::class      => LinkFactoryConfigurator::class,
                WebExecutor::class       => WebExecutorConfigurator::class,
                IThreadManager::class    => ThreadManagerConfigurator::class,
                IStoreManager::class     => StoreManagerConfigurator::class,
            ];
        }
    }