From d8c4f3eeb8eb71aceb2c303cf7e8e7a097c1b05f Mon Sep 17 00:00:00 2001 From: Manuel Raynaud Date: Wed, 3 Apr 2013 12:47:49 +0200 Subject: [PATCH] refactor config process using only xml config file --- core/lib/Thelia/Core/Bundle/TheliaBundle.php | 74 +---- .../Compiler/RegisterListenersPass.php | 72 ++++ .../Loader/XmlFileLoader.php | 314 ++++++++++++++++++ .../Loader/schema/dic/config/thelia-1.0.xsd | 157 +++++++++ core/lib/Thelia/Core/Template/Parser.php | 2 +- core/lib/Thelia/Core/Thelia.php | 103 +----- core/lib/Thelia/config.xml | 68 +++- local/plugins/Test/Config/config.xml | 15 +- 8 files changed, 638 insertions(+), 167 deletions(-) create mode 100644 core/lib/Thelia/Core/DependencyInjection/Compiler/RegisterListenersPass.php create mode 100644 core/lib/Thelia/Core/DependencyInjection/Loader/XmlFileLoader.php create mode 100644 core/lib/Thelia/Core/DependencyInjection/Loader/schema/dic/config/thelia-1.0.xsd diff --git a/core/lib/Thelia/Core/Bundle/TheliaBundle.php b/core/lib/Thelia/Core/Bundle/TheliaBundle.php index 50d542efc..507d54453 100644 --- a/core/lib/Thelia/Core/Bundle/TheliaBundle.php +++ b/core/lib/Thelia/Core/Bundle/TheliaBundle.php @@ -27,6 +27,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Scope; +use Thelia\Core\DependencyInjection\Compiler\RegisterListenersPass; + /** * First Bundle use in Thelia * It initialize dependency injection container. @@ -49,76 +51,10 @@ class TheliaBundle extends Bundle public function build(ContainerBuilder $container) { - $container->addScope( new Scope('request')); + parent::build($container); - $container->register('request', 'Symfony\Component\HttpFoundation\Request') - ->setSynthetic(true); - - $container->register('controller.default','Thelia\Controller\DefaultController'); - $container->register('matcher.default','Thelia\Routing\Matcher\DefaultMatcher') - ->addArgument(new Reference('controller.default')); - - $container->register('matcher.action', 'Thelia\Routing\Matcher\ActionMatcher'); - - $container->register('matcher','Thelia\Routing\TheliaMatcherCollection') - ->addMethodCall('add', array(new Reference('matcher.default'), -255)) - ->addMethodCall('add', array(new Reference('matcher.action'), -200)) - //->addMethodCall('add','a matcher class (instance or class name) - - ; - - $container->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver'); - - - $container->register('parser','Thelia\Core\Template\Parser') - ->addArgument(new Reference('service_container')) - ; - /** - * RouterListener implements EventSubscriberInterface and listen for kernel.request event - */ - $container->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener') - ->addArgument(new Reference('matcher')) - ; - - /** - * @TODO add an other listener on kernel.request for checking some params Like check if User is log in, set the language and other. - * - * $container->register() - * - * - * $container->register('listener.request', 'Thelia\Core\EventListener\RequestListener') - * ->addArgument(new Reference(''); - * ; - */ - - $container->register('thelia.listener.view','Thelia\Core\EventListener\ViewListener') - ->addArgument(new Reference('service_container')) - ; - - - - $container->register('dispatcher','Symfony\Component\EventDispatcher\EventDispatcher') - ->addArgument(new Reference('service_container')) - ->addMethodCall('addSubscriber', array(new Reference('listener.router'))) - ->addMethodCall('addSubscriber', array(new Reference('thelia.listener.view'))) - ; - - - // TODO : save listener from plugins - - $container->getDefinition('matcher.action')->addMethodCall("setDispatcher", array(new Reference('dispatcher'))); - - $container->register('http_kernel','Thelia\Core\TheliaHttpKernel') - ->addArgument(new Reference('dispatcher')) - ->addArgument(new Reference('service_container')) - ->addArgument(new Reference('resolver')) - ; - - // DEFINE DEFAULT PARAMETER LIKE - - /** - * @TODO learn about container compilation - */ + $container->addScope(new Scope('request')); + $container->addCompilerPass(new RegisterListenersPass()); } } diff --git a/core/lib/Thelia/Core/DependencyInjection/Compiler/RegisterListenersPass.php b/core/lib/Thelia/Core/DependencyInjection/Compiler/RegisterListenersPass.php new file mode 100644 index 000000000..881ddaceb --- /dev/null +++ b/core/lib/Thelia/Core/DependencyInjection/Compiler/RegisterListenersPass.php @@ -0,0 +1,72 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + + +class RegisterListenersPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('event_dispatcher')) { + return; + } + + $definition = $container->getDefinition('event_dispatcher'); + + foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $events) { + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace(array( + '/(?<=\b)[a-z]/ie', + '/[^a-z0-9]/i' + ), array('strtoupper("\\0")', ''), $event['event']); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) { + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getDefinition($id)->getClass(); + + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/DependencyInjection/Loader/XmlFileLoader.php b/core/lib/Thelia/Core/DependencyInjection/Loader/XmlFileLoader.php new file mode 100644 index 000000000..39e18a6ab --- /dev/null +++ b/core/lib/Thelia/Core/DependencyInjection/Loader/XmlFileLoader.php @@ -0,0 +1,314 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\DependencyInjection\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader as XmlLoader; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\SimpleXMLElement; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Loader\FileLoader; + +class XmlFileLoader extends FileLoader +{ + /** + * Loads an XML file. + * + * @param mixed $file The resource + * @param string $type The resource type + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $xml = $this->parseFile($path); + $xml->registerXPathNamespace('config', 'http://thelia.net/schema/dic/config'); + + $this->container->addResource(new FileResource($path)); + + $this->parseDefinitions($xml, $path); + + $this->parseLoops($xml); + + $this->parseFilters($xml); + + $this->parseBaseParams($xml); + + $this->parseTestLoops($xml); + } + + protected function parseLoops(SimpleXMLElement $xml) + { + if (false === $loops = $xml->xpath('//config:loops/config:loop')) { + return; + } + try { + $loopConfig = $this->container->getParameter("Tpex.loop"); + } catch (\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException $e) { + $loopConfig = array(); + } + + foreach ($loops as $loop) { + $loopConfig[$loop->getAttributeAsPhp("name")] = $loop->getAttributeAsPhp("class"); + } + + $this->container->setParameter("Tpex.loop", $loopConfig); + } + + protected function parseFilters(SimpleXMLElement $xml) + { + if (false === $filters = $xml->xpath('//config:filters/config:filter')) { + return; + } + try { + $filterConfig = $this->container->getParameter("Tpex.filter"); + } catch (\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException $e) { + $filterConfig = array(); + } + + foreach ($filters as $filter) { + $filterConfig[$filter->getAttributeAsPhp("name")] = $filter->getAttributeAsPhp("class"); + } + + $this->container->setParameter("Tpex.filter", $filterConfig); + } + + protected function parseBaseParams(SimpleXMLElement $xml) + { + if (false === $baseParams = $xml->xpath('//config:baseParams/config:baseParam')) { + return; + } + try { + $baseParamConfig = $this->container->getParameter("Tpex.baseParam"); + } catch (\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException $e) { + $baseParamConfig = array(); + } + + foreach ($baseParams as $baseParam) { + $baseParamConfig[$baseParam->getAttributeAsPhp("name")] = $baseParam->getAttributeAsPhp("class"); + } + + $this->container->setParameter("Tpex.baseParam", $baseParamConfig); + } + + protected function parseTestLoops(SimpleXMLElement $xml) + { + if (false === $testLoops = $xml->xpath('//config:testLoops/config:testLoop')) { + return; + } + try { + $baseParamConfig = $this->container->getParameter("Tpex.baseParam"); + } catch (\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException $e) { + $baseParamConfig = array(); + } + + foreach ($testLoops as $testLoop) { + $baseParamConfig[$testLoop->getAttributeAsPhp("name")] = $testLoop->getAttributeAsPhp("class"); + } + + $this->container->setParameter("Tpex.testLoop", $baseParamConfig); + } + + /** + * Parses multiple definitions + * + * @param SimpleXMLElement $xml + * @param string $file + */ + protected function parseDefinitions(SimpleXMLElement $xml, $file) + { + if (false === $services = $xml->xpath('//config:services/config:service')) { + return; + } + foreach ($services as $service) { + $this->parseDefinition((string) $service['id'], $service, $file); + } + } + + /** + * Parses an individual Definition + * + * @param string $id + * @param SimpleXMLElement $service + * @param string $file + */ + protected function parseDefinition($id, $service, $file) + { + + if ((string) $service['alias']) { + $public = true; + if (isset($service['public'])) { + $public = $service->getAttributeAsPhp('public'); + } + $this->container->setAlias($id, new Alias((string) $service['alias'], $public)); + + return; + } + + if (isset($service['parent'])) { + $definition = new DefinitionDecorator((string) $service['parent']); + } else { + $definition = new Definition(); + } + + foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) { + if (isset($service[$key])) { + $method = 'set'.str_replace('-', '', $key); + $definition->$method((string) $service->getAttributeAsPhp($key)); + } + } + + if ($service->file) { + $definition->setFile((string) $service->file); + } + + $definition->setArguments($service->getArgumentsAsPhp('argument')); + $definition->setProperties($service->getArgumentsAsPhp('property')); + + if (isset($service->configurator)) { + if (isset($service->configurator['function'])) { + $definition->setConfigurator((string) $service->configurator['function']); + } else { + if (isset($service->configurator['service'])) { + $class = new Reference((string) $service->configurator['service'], ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false); + } else { + $class = (string) $service->configurator['class']; + } + + $definition->setConfigurator(array($class, (string) $service->configurator['method'])); + } + } + + foreach ($service->call as $call) { + $definition->addMethodCall((string) $call['method'], $call->getArgumentsAsPhp('argument')); + } + + foreach ($service->tag as $tag) { + $parameters = array(); + foreach ($tag->attributes() as $name => $value) { + if ('name' === $name) { + continue; + } + + $parameters[$name] = SimpleXMLElement::phpize($value); + } + + $definition->addTag((string) $tag['name'], $parameters); + } + + $this->container->setDefinition($id, $definition); + } + + /** + * Parses a XML file. + * + * @param string $file Path to a file + * + * @return SimpleXMLElement + * + * @throws InvalidArgumentException When loading of XML file returns error + */ + protected function parseFile($file) + { + try { + $dom = XmlUtils::loadFile($file, array($this, 'validateSchema')); + } catch (\InvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + return simplexml_import_dom($dom, 'Symfony\\Component\\DependencyInjection\\SimpleXMLElement'); + } + + /** + * Validates a documents XML schema. + * + * @param \DOMDocument $dom + * + * @return Boolean + * + * @throws RuntimeException When extension references a non-existent XSD file + */ + public function validateSchema(\DOMDocument $dom) + { + $schemaLocations = array('http://thelia.net/schema/dic/config' => str_replace('\\', '/',__DIR__.'/schema/dic/config/thelia-1.0.xsd')); + + $tmpfiles = array(); + $imports = ''; + foreach ($schemaLocations as $namespace => $location) { + $parts = explode('/', $location); + if (0 === stripos($location, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); + if ($tmpfile) { + copy($location, $tmpfile); + $tmpfiles[] = $tmpfile; + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } + } + $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $location = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); + + $imports .= sprintf(' '."\n", $namespace, $location); + } + + $source = << + + + +$imports + +EOF + ; + + $valid = @$dom->schemaValidateSource($source); + + foreach ($tmpfiles as $tmpfile) { + @unlink($tmpfile); + } + + return $valid; + } + + /** + * Returns true if this class supports the given resource. + * + * @param mixed $resource A resource + * @param string $type The resource type + * + * @return Boolean true if this class supports the given resource, false otherwise + */ + public function supports($resource, $type = null) + { + // TODO: Implement supports() method. + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/DependencyInjection/Loader/schema/dic/config/thelia-1.0.xsd b/core/lib/Thelia/Core/DependencyInjection/Loader/schema/dic/config/thelia-1.0.xsd new file mode 100644 index 000000000..64563b0cb --- /dev/null +++ b/core/lib/Thelia/Core/DependencyInjection/Loader/schema/dic/config/thelia-1.0.xsd @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/lib/Thelia/Core/Template/Parser.php b/core/lib/Thelia/Core/Template/Parser.php index 346ab2bfc..1a4d3c8be 100644 --- a/core/lib/Thelia/Core/Template/Parser.php +++ b/core/lib/Thelia/Core/Template/Parser.php @@ -150,7 +150,7 @@ class Parser implements ParserInterface $tpex = new Tpex(); - $tpex->init($this->container->get("request"), $this->container->get("dispatcher"), $content, THELIA_TEMPLATE_DIR . rtrim($this->template, "/") . "/"); + $tpex->init($this->container->get("request"), $this->container->get("event_dispatcher"), $content, THELIA_TEMPLATE_DIR . rtrim($this->template, "/") . "/"); $tpex->configure( $this->container->getParameter("Tpex.loop"), $this->container->getParameter("Tpex.filter"), diff --git a/core/lib/Thelia/Core/Thelia.php b/core/lib/Thelia/Core/Thelia.php index 08980f01d..03ff5324d 100644 --- a/core/lib/Thelia/Core/Thelia.php +++ b/core/lib/Thelia/Core/Thelia.php @@ -47,8 +47,9 @@ use Thelia\Core\Bundle; use Thelia\Log\Tlog; use Thelia\Config\DatabaseConfiguration; use Thelia\Config\DefinePropel; -use Thelia\Config\Dumper\TpexConfigDumper; use Thelia\Core\TheliaContainerBuilder; +use Thelia\Core\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\Config\FileLocator; use Propel; use PropelConfiguration; @@ -101,106 +102,23 @@ class Thelia extends Kernel * Initialize all plugins * */ - public function loadConfiguration(ContainerBuilder $container) + protected function loadConfiguration(ContainerBuilder $container) { - /** - * TODO : - * - Retrieve all actives plugins - * - load config (create a cache and use this cache - */ - - - /** - * Set all listener here. - * Use $dispatcher->addSubscriber or addListener ? - */ - $dispatcher = $container->get("dispatcher"); - - - - - /** - * manage Tpex configuration here - */ - $container = $this->generateTpexConfig($container); - - - - return $container; - - } - - protected function generateTpexConfig(ContainerBuilder $container) - { - $loopConfig = array(); - $filterConfig = array(); - $baseParamConfig = array(); - $loopTestConfig = array(); - $resources = array(); - - //load master config, can be overload using modules - - $masterConfigFile = THELIA_ROOT . "/core/lib/Thelia/config.xml"; - - if (file_exists($masterConfigFile)) { - $container->addResource(new FileResource($masterConfigFile)); - - $dom = XmlUtils::loadFile($masterConfigFile); - - $loopConfig = $this->processConfig($dom->getElementsByTagName("loop")); - - $filterConfig = $this->processConfig($dom->getElementsByTagName("filter")); - - $baseParamConfig = $this->processConfig($dom->getElementsByTagName("baseParam")); - - $loopTestConfig = $this->processConfig($dom->getElementsByTagName("testLoop")); - } + $loader = new XmlFileLoader($container, new FileLocator(THELIA_ROOT . "/core/lib/Thelia")); + $loader->load("config.xml"); $modules = \Thelia\Model\ModuleQuery::getActivated(); foreach ($modules as $module) { - $configFile = THELIA_PLUGIN_DIR . "/" . ucfirst($module->getCode()) . "/Config/config.xml"; - if (file_exists($configFile)) { - $container->addResource(new FileResource($configFile)); - $dom = XmlUtils::loadFile($configFile); - $loopConfig = array_merge($loopConfig, $this->processConfig($dom->getElementsByTagName("loop"))); - - $filterConfig = array_merge($filterConfig, $this->processConfig($dom->getElementsByTagName("filter"))); - - $baseParamConfig = array_merge( - $baseParamConfig, - $this->processConfig($dom->getElementsByTagName("baseParam")) - ); - - $loopTestConfig = array_merge( - $loopTestConfig, - $this->processConfig($dom->getElementsByTagName("testLoop")) - ); + try { + $loader = new XmlFileLoader($container, new FileLocator(THELIA_PLUGIN_DIR . "/" . ucfirst($module->getCode()) . "/Config")); + $loader->load("config.xml"); + } catch(\InvalidArgumentException $e) { } } - - $container->setParameter("Tpex.loop", $loopConfig); - - $container->setParameter("Tpex.filter", $filterConfig); - - $container->setParameter("Tpex.baseParam", $baseParamConfig); - - $container->setParameter("Tpex.testLoop", $loopTestConfig); - - return $container; - } - - protected function processConfig(\DOMNodeList $elements) - { - $result = array(); - for ($i = 0; $i < $elements->length; $i ++) { - $element = XmlUtils::convertDomElementToArray($elements->item($i)); - $result[$element["name"]] = $element["class"]; - } - return $result; } /** @@ -233,8 +151,7 @@ class Thelia extends Kernel { $container = parent::buildContainer(); - $container = $this->loadConfiguration($container); - + $this->loadConfiguration($container); $container->customCompile(); return $container; diff --git a/core/lib/Thelia/config.xml b/core/lib/Thelia/config.xml index f51d6f494..e3ae73f70 100644 --- a/core/lib/Thelia/config.xml +++ b/core/lib/Thelia/config.xml @@ -1,8 +1,72 @@ - + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + -255 + + + + -200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/local/plugins/Test/Config/config.xml b/local/plugins/Test/Config/config.xml index f9c2aeb0c..d68e49c37 100644 --- a/local/plugins/Test/Config/config.xml +++ b/local/plugins/Test/Config/config.xml @@ -1,6 +1,17 @@ - + + + + + + + + - \ No newline at end of file + + +