diff --git a/DependencyInjection/Builder/ClassFilterBuilder.php b/DependencyInjection/Builder/ClassFilterBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..d3020bd7302aa35827f5ada21da94e9e15ee4f5d --- /dev/null +++ b/DependencyInjection/Builder/ClassFilterBuilder.php @@ -0,0 +1,140 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\DependencyInjection\Builder; + +use Irstea\PlantUmlBundle\Model\Filter\ClassFilter; +use Irstea\PlantUmlBundle\Model\Filter\Composite\AllFilter; +use Irstea\PlantUmlBundle\Model\Filter\DirectoryFilter; +use Irstea\PlantUmlBundle\Model\Filter\NamespaceFilter; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Description of ClassFilterBuilder + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class ClassFilterBuilder +{ + /** + * @var ContainerBuilder + */ + private $container; + + /** + * @var Reference[] + */ + private $filters = []; + + /** + * @var string[] + */ + static private $filterClasses = [ + 'classes' => ClassFilter::class, + 'directories' => DirectoryFilter::class, + 'namespaces' => NamespaceFilter::class + ]; + + /** + * @param \Irstea\PlantUmlBundle\DependencyInjection\Builder\ContainerBuilder $container + */ + public function __construct(ContainerBuilder $container) + { + $this->container = $container; + } + + /** + * @param array $config + * @return Reference + */ + public function build(array $config) + { + $filters = $this->normalize(array_intersect_key($config, ['include' => true, 'exclude' => true])); + if ($filters === null) { + return null; + } + + $hash = sha1(serialize($filters)); + if (!key_exists($hash, $this->filters)) { + return $this->filters[$hash] = $this->doBuild("irstea.plant_uml.filters.$hash", $filters); + } + + return $this->filters[$hash]; + } + + /** + * @param mixed $data + * @return mixed + */ + protected function normalize($data) + { + if ($data === null || $data === []) { + return null; + } + if (is_array($data)) { + $res = []; + foreach($data as $k => $v) { + $normalized = $this->normalize($v); + if (!empty($normalized)) { + $res[$k] = $normalized; + } + } + return empty($res) ? null : $res; + } + return $data; + } + + /** + * @param type $id + * @param array $config + * @return Reference + */ + protected function doBuild($id, array $config) + { + $filters = []; + + if (isset($config['include'])) { + $this->buildSubFilter($filters, "$id.include", $config['include'], false); + } + if (isset($config['exclude'])) { + $this->buildSubFilter($filters, "$id.exclude", $config['exclude'], true); + } + + if (empty($filters)) { + return null; + } + + if (count($filters) === 1) { + return $filters[0]; + } + + $this->container->setDefinition($id, new Definition(AllFilter::class, [$filters])); + return new Reference($id); + } + + /** + * @param array $filters + * @param string $id + * @param array $config + * @param bool $notFound + */ + protected function buildSubFilter(array &$filters, $id, array $config, $notFound) + { + foreach (self::$filterClasses as $key => $class) { + if (!isset($config[$key])) { + continue; + } + $subId = "$id.$key"; + $def = new Definition($class, [$config[$key], $notFound]); + $this->container->setDefinition($subId, $def); + $filters[] = new Reference($subId); + } + } +} diff --git a/DependencyInjection/Builder/GraphDefinitionBuilder.php b/DependencyInjection/Builder/GraphDefinitionBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..1be9d730bac9c51fa5b9a1a59e672467df0e626f --- /dev/null +++ b/DependencyInjection/Builder/GraphDefinitionBuilder.php @@ -0,0 +1,253 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\DependencyInjection\Builder; + +use Irstea\PlantUmlBundle\Doctrine\EntityFinder; +use Irstea\PlantUmlBundle\Finder\ClassFinder; +use Irstea\PlantUmlBundle\Finder\FilteringFinder; +use Irstea\PlantUmlBundle\Model\ClassVisitor; +use Irstea\PlantUmlBundle\Model\Graph; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Description of GraphDefinitionBuilder + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class GraphDefinitionBuilder +{ + /** + * @var ContainerBuilder + */ + private $container; + + /** + * @var string + */ + private $baseId; + + /** + * @var array + */ + private $config; + + /** + * @var ClassFilterBuilder + */ + private $filterBuilder; + + /** + * @var Reference + */ + private $entityManager; + + /** + * + * @var Definition + */ + private $definition = null; + + /** + * + * @param ContainerBuilder $container + * @param string $baseId + * @param array $config + */ + public function __construct(ContainerBuilder $container, $baseId, array $config, ClassFilterBuilder $filterBuilder) + { + $this->container = $container; + $this->baseId = $baseId; + $this->config = $config; + $this->filterBuilder = $filterBuilder; + + $emId = $config['sources']['entity_manager']; + $this->entityManager = new Reference("doctrine.orm.${emId}_entity_manager"); + } + + /** + * @return Definition + */ + public function build() + { + if (!$this->definition) { + return $this->definition = $this->doBuild(); + } + return $this->definition; + } + + /** + * @return Definition + */ + protected function doBuild() + { + list($source, $sourceFilter) = $this->buildSources(); + + $layoutFilter = $this->filterBuilder->build($this->config['layout']) ?: $sourceFilter; + $decorator = $this->buildFilteredDecorator(); + $namespace = $this->buildNamespace(); + + $visitor = $this->setDefinition("visitor", ClassVisitor::class, $decorator, $layoutFilter, $namespace); + + return new Definition(Graph::class, [$visitor, $source]); + } + + /** + * @return Refernce[] + */ + protected function buildSources() + { + $finder = $this->buildFinder(); + $filter = $this->filterBuilder->build($this->config['sources']); + + if ($filter) { + $filtered = $this->setDefinition("finder", FilteringFinder::class, $finder, $filter); + return [$filtered, $filter]; + } + + return [$finder, null]; + } + + /** + * @return Reference + */ + protected function buildFinder() + { + $config = $this->config['sources']; + + switch($config['type']) { + case 'entities': + return $this->setDefinition("finder.entities", EntityFinder::class, $this->entityManager); + case 'classes': + return $this->setDefinition("finder.classes", ClassFinder::class, $config['directories']); + } + } + + /** + * @return Reference + */ + protected function buildFilteredDecorator() + { + $decorator = $this->buildDecorator(); + if (!$decorator) { + return $decorator; + } + + $filter = $this->filterBuilder->build($this->config['decoration']); + if (!$filter) { + return $decorator; + } + + return $this->setDefinitionDecorator('decorator', 'irstea.plant_uml.decorator.filtered.template', $decorator, $filter); + } + + /** + * @return Reference + */ + protected function buildDecorator() + { + $config = $this->config['decoration']['decorators']; + + if (empty($config)) { + return null; + } + + if (count($config) === 1) { + return $this->buildTypedDecorator($config[0]); + } + + $decorators = []; + foreach ($config as $type) { + $decorators[] = $this->buildTypedDecorator($type); + } + + return $this->setDefinitionDecorator( + 'decorator.all', + 'irstea.plant_uml.decorator.composite.template', + $decorators + ); + } + + /** + * @param string $type + * @return Reference + */ + protected function buildTypedDecorator($type) + { + if (in_array($type, ['entity', 'associations', 'fields'])) { + return $this->setDefinitionDecorator( + "decorator.$type", + "irstea.plant_uml.decorator.$type.template", + $this->entityManager + ); + } + + return new Reference("irstea.plant_uml.decorator.$type"); + } + + /** +œ * @return Reference + */ + protected function buildNamespace() + { + $type = $this->config['layout']['namespaces']; + + if ($type === "entities") { + return $this->setDefinitionDecorator( + "namespace.$type", + "irstea.plant_uml.namespaces.$type.template", + $this->entityManager + ); + } + + return new Reference("irstea.plant_uml.namespaces.$type"); + } + + /** + * @param string $localId + * @param string|Definition $classOrDef + * @param array ...$arguments + * @return Reference + */ + protected function setDefinition($localId, $classOrDef, ...$arguments) + { + if ($classOrDef instanceof Definition) { + $definition = $classOrDef; + } else { + $definition = new Definition($classOrDef, $arguments); + } + $id = $this->globalId($localId); + $this->container->setDefinition($id, $definition); + return new Reference($id); + } + + /** + * @param string $localId + * @param string $templateId + * @param array ...$arguments + * @return Reference + */ + protected function setDefinitionDecorator($localId, $templateId, ...$arguments) + { + $def = new DefinitionDecorator($templateId); + $def->setArguments($arguments); + return $this->setDefinition($localId, $def); + } + + /** + * @param string $localId + * @return string + */ + protected function globalId($localId) + { + return "{$this->baseId}.$localId"; + } +} diff --git a/DependencyInjection/IrsteaPlantUmlExtension.php b/DependencyInjection/IrsteaPlantUmlExtension.php index 6c8a63cc78cd0504914ed34a16462b23a393b92c..3adc4aa357143f3b75f3f0f4ddf6c33c50d36964 100644 --- a/DependencyInjection/IrsteaPlantUmlExtension.php +++ b/DependencyInjection/IrsteaPlantUmlExtension.php @@ -8,18 +8,12 @@ namespace Irstea\PlantUmlBundle\DependencyInjection; -use Irstea\PlantUmlBundle\Doctrine\EntityFinder; -use Irstea\PlantUmlBundle\Finder\ClassFinder; -use Irstea\PlantUmlBundle\Finder\FilteringFinder; -use Irstea\PlantUmlBundle\Model\ClassVisitor; -use Irstea\PlantUmlBundle\Model\Graph; +use Irstea\PlantUmlBundle\DependencyInjection\Builder\ClassFilterBuilder; +use Irstea\PlantUmlBundle\DependencyInjection\Builder\GraphDefinitionBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Loader; -use Symfony\Component\DependencyInjection\Reference; /** * Description of IrsteaPlantUmlExtension @@ -41,184 +35,13 @@ class IrsteaPlantUmlExtension extends Extension $container->setParameter('irstea_plant_uml.binaries.dot', $config['binaries']['dot']); $container->setParameter('irstea_plant_uml.output.directory', $config['output']['directory']); $container->setParameter('irstea_plant_uml.output.format', $config['output']['format']); - - foreach($config['graphs'] as $key => $graph) { - $this->loadGraph($key, $graph, $container); - } $container->setParameter('irstea_plant_uml.graph_keys', array_keys($config['graphs'])); - } - - public function loadGraph($key, array $config, ContainerBuilder $container) - { - $id = "irstea_plant_uml.graph.$key"; - - $emName = $config['sources']['entity_manager']; - $em = new Reference("doctrine.orm.${emName}_entity_manager"); - - list($source, $defaultFilter) = $this->loadSources($id, $em, $config['sources'], $container); - - $visitor = $this->addService( - $container, - "$id.visitor", - ClassVisitor::class, - [ - $this->loadDecorator($id, $em, $config['decoration'], $defaultFilter, $container), - $this->loadFilter($id, $config['layout'], $container), - $this->loadNamespace($id, $em, $config['layout']['namespaces'], $container) - ] - ); - - $container->setDefinition($id, new Definition(Graph::class, [$visitor, $source])) - ->setPublic(true) - ->setLazy(true); - } - - /** - * @param string $id - * @param array $config - * @param ContainerBuilder $container - * @return Refernce[] - */ - protected function loadSources($id, Reference $em, array $config, ContainerBuilder $container) - { - $finderId = "$id.finder"; - $filter = $this->loadFilter($finderId, $config, $container); - if ($filter) { - $inner = $this->loadFinder("$finderId.inner", $em, $config, $container); - $finder = $this->addService($container, $finderId, FilteringFinder::class, [$inner, $filter]); - } else { - $finder = $this->loadFinder($finderId, $em, $config, $container); + $filterBuilder = new ClassFilterBuilder($container); + foreach ($config['graphs'] as $key => $graph) { + $id = "irstea_plant_uml.graph.$key"; + $builder = new GraphDefinitionBuilder($container, $id, $graph, $filterBuilder); + $container->setDefinition($id, $builder->build()); } - - return [$finder, $filter]; - } - - /** - * @param string $id - * @param Reference $em - * @param array $config - * @param ContainerBuilder $container - * @return Reference - */ - protected function loadFinder($id, Reference $em, array $config, ContainerBuilder $container) - { - switch($config['type']) { - case 'entities': - return $this->addService($container, $id, EntityFinder::class, [$em]); - case 'classes': - return $this->addService($container, $id, ClassFinder::class, [$config['directories']]); - } - } - - /** - * - * @param string $id - * @param Reference $em - * @param array $config - * @param Reference $defaultFilter - * @param ContainerBuilder $container - * @return Reference - */ - protected function loadDecorator($id, Reference $em, array $config, Reference $defaultFilter, ContainerBuilder $container) - { - if (empty($config['decorators'])) { - return new Reference('irstea.plant_uml.decorator.null'); - } - - $decoratorId = "$id.decorator"; - - $decorators = []; - foreach($config['decorators'] as $i => $type) { - $decorators[] = $this->loadTypedDecorator("$decoratorId.$i", $em, $type, $container); - } - - if (count($decorators) === 1) { - $container->setAlias($decoratorId, "$decoratorId.0"); - } else { - $container - ->setDefinition($decoratorId, new DefinitionDecorator('irstea.plant_uml.decorator.composite.template')) - ->setArguments([$decorators]); - } - - $filter = $this->loadFilter($decoratorId, $config, $container) ?: $defaultFilter; - if ($filter) { - $container - ->setDefinition("$decoratorId.decorator", new DefinitionDecorator('irstea.plant_uml.decorator.filtered.template')) - ->setDecoratedService($decoratorId, "inner_decorator") - ->setArguments([new Reference("inner_decorator"), $filter]); - } - - return new Reference($decoratorId); - } - - /** - * @param string $id - * @param Reference $em - * @param string $type - * @param ContainerBuilder $container - * @return Reference - */ - protected function loadTypedDecorator($id, Reference $em, $type, ContainerBuilder $container) - { - if (in_array($type, ['entity', 'associations', 'fields'])) { - $container - ->setDefinition($id, new DefinitionDecorator("irstea.plant_uml.decorator.$type.template")) - ->setArguments([$em]); - return new Reference($id); - } - - return new Reference("irstea.plant_uml.decorator.$type"); - } - - /** - * @param string $id - * @param Reference $em - * @param string $type - * @param ContainerBuilder $container - * @return Reference - */ - protected function loadNamespace($id, Reference $em, $type, ContainerBuilder $container) - { - if ($type === "entities") { - $namespaceId = "$id.namespace"; - $container - ->setDefinition($namespaceId, new DefinitionDecorator('irstea.plant_uml.namespaces.entities.template')) - ->setArguments([$em]); - return new Reference($namespaceId); - } - - return new Reference("irstea.plant_uml.namespaces.$type"); - } - - /** - * @param string $id - * @param array $config - * @param ContainerBuilder $container - * @return Reference - */ - protected function loadFilter($id, array $config, ContainerBuilder $container) - { - $filterId = "$id.filter"; - $container - ->setDefinition($filterId, new DefinitionDecorator("irstea.plant_uml.filter.template")) - ->setArguments([ - isset($config['include']) ? $config['include'] : [], - isset($config['exclude']) ? $config['exclude'] : [], - ]); - return new Reference($filterId); - } - - /** - * - * @param ContainerBuilder $container - * @param string $id - * @param Definition $def - * @return Reference - */ - protected function addService(ContainerBuilder $container, $id, $class, array $arguments = []) - { - $container->setDefinition($id, new Definition($class, $arguments)); - return new Reference($id); } } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index c6d838724313eab97a54775019ba4892f5b92fe7..4459e3623eb59004f438f4a49cc005be35e0b192 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -1,16 +1,6 @@ parameters: ~ services: - irstea.plant_uml.filter.factory: - class: Irstea\PlantUmlBundle\Factory\FilterFactory - arguments: [@kernel] - - irstea.plant_uml.filter.template: - class: Irstea\PlantUmlBundle\Model\ClassFilterInterface - abstract: true - factory: ["@irstea.plant_uml.filter.factory", create] - public: false - irstea.plant_uml.finder.classes.template: class: Irstea\PlantUmlBundle\Finder\ClassFinder abstract: true