Commit de9253f1 authored by Guillaume Perréal's avatar Guillaume Perréal
Browse files

Refactorisation de la création des graphs.

parent 7e3455bc
......@@ -6,48 +6,12 @@
*/
namespace Irstea\PlantUmlBundle\Command;
use Doctrine\ORM\EntityManagerInterface;
use Irstea\PlantUmlBundle\Doctrine\AssociationDecorator;
use Irstea\PlantUmlBundle\Doctrine\DoctrineNamespace;
use Irstea\PlantUmlBundle\Doctrine\EntityDecorator;
use Irstea\PlantUmlBundle\Doctrine\EntityFinder;
use Irstea\PlantUmlBundle\Doctrine\FieldDecorator;
use Irstea\PlantUmlBundle\Finder\ClassFinder;
use Irstea\PlantUmlBundle\Finder\FilteringFinder;
use Irstea\PlantUmlBundle\Finder\FinderInterface;
use Irstea\PlantUmlBundle\Model\ClassFilterInterface;
use Irstea\PlantUmlBundle\Model\ClassVisitor;
use Irstea\PlantUmlBundle\Model\Decorator\AttributeDecorator;
use Irstea\PlantUmlBundle\Model\Decorator\CompositeDecorator;
use Irstea\PlantUmlBundle\Model\Decorator\FilteringDecorator;
use Irstea\PlantUmlBundle\Model\Decorator\InheritanceDecorator;
use Irstea\PlantUmlBundle\Model\Decorator\InterfaceDecorator;
use Irstea\PlantUmlBundle\Model\Decorator\MethodDecorator;
use Irstea\PlantUmlBundle\Model\Decorator\NullDecorator;
use Irstea\PlantUmlBundle\Model\Decorator\TraitDecorator;
use Irstea\PlantUmlBundle\Model\DecoratorInterface;
use Irstea\PlantUmlBundle\Model\Filter\AcceptAllFilter;
use Irstea\PlantUmlBundle\Model\Filter\Composite\AllFilter;
use Irstea\PlantUmlBundle\Model\Filter\DirectoryFilter;
use Irstea\PlantUmlBundle\Model\Filter\NamespaceFilter;
use Irstea\PlantUmlBundle\Model\Graph;
use Irstea\PlantUmlBundle\Model\Namespace_\BundleNamespace;
use Irstea\PlantUmlBundle\Model\Namespace_\FlatNamespace;
use Irstea\PlantUmlBundle\Model\Namespace_\Php\RootNamespace;
use Irstea\PlantUmlBundle\Model\NamespaceInterface;
use Irstea\PlantUmlBundle\Writer\OutputWriter;
use ReflectionClass;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
use Symfony\Component\Security\Core\Exception\RuntimeException;
/**
* Description of ImportAffiliationCommand
......@@ -56,21 +20,6 @@ use Symfony\Component\Security\Core\Exception\RuntimeException;
*/
class GenerateCommand extends ContainerAwareCommand
{
/**
* @var string[]
*/
private $bundles;
/**
* @var KernelInterface
*/
private $kernel;
/**
* @var EntityManagerInterface
*/
private $entityManager;
protected function configure()
{
$this
......@@ -79,16 +28,6 @@ class GenerateCommand extends ContainerAwareCommand
->addArgument('graph', InputArgument::REQUIRED, 'Nom du graphe à générer');
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
parent::initialize($input, $output);
// @todo: DI
$this->bundles = $this->getContainer()->getParameter('kernel.bundles');
$this->kernel = $this->getContainer()->get('kernel');
$this->entityManager = $this->getContainer()->get('doctrine.orm.entity_manager');
}
/**
*
* @param InputInterface $input
......@@ -99,224 +38,15 @@ class GenerateCommand extends ContainerAwareCommand
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('graph');
$graphs = $this->getContainer()->getParameter('irstea_plant_uml.graphs');
if (!isset($graphs[$name])) {
$serviceId = "irstea_plant_uml.graph.$name";
if (!$this->getContainer()->has($serviceId)) {
throw new InvalidArgumentException("Le graphe '$name' n'est pas défini.");
}
$config = $graphs[$name];
list($finder, $filter) = $this->buildFinder($config['sources']);
$graph = new Graph(
new ClassVisitor(
$this->buildDecorator($config['decoration'], $filter),
$this->buildFilter($config['layout']),
$this->buildNamespace($config['layout']['namespaces'])
),
$finder
);
$graph->visitAll();
$writer = new OutputWriter($output);
$graph = $this->getContainer()->get($serviceId);
$graph->visitAll();
$graph->writeTo($writer);
}
/**
* @param array $config
* @return FinderInterface
*/
protected function buildFinder(array $config)
{
switch($config['type']) {
case 'entities':
$finder = $this->buildEntityFinder($config['entity_manager']);
break;
case 'classes':
$finder = $this->buildClassFinder($config['directories']);
break;
}
$filter = $this->buildFilter($config);
if (!$filter) {
return [$finder, null];
}
return [new FilteringFinder($finder, $filter), $filter];
}
/**
* @param array $directories
* @return FinderInterface
*/
protected function buildClassFinder(array $directories)
{
return new ClassFinder($this->parseDirectories($directories));
}
/**
* @param string $managerName
* @return FinderInterface
*/
protected function buildEntityFinder($managerName)
{
return new EntityFinder(
$this->getContainer()->get('doctrine')->getManager($managerName)
);
}
/**
* @param array $config
* @return DecoratorInterface
* @throws RuntimeException
*/
protected function buildDecorator(array $config, ClassFilterInterface $defaultFilter = null)
{
if (empty($config['decorators'])) {
return NullDecorator::instance();
}
$decorators = [];
foreach($config['decorators'] as $type) {
$decorators[] = $this->buildTypedDecorator($type);
}
if (count($decorators) === 1) {
$decorator = $decorators[0];
} else {
$decorator = new CompositeDecorator($decorators);
}
$filter = $this->buildFilter($config) ?: $defaultFilter;
if ($filter) {
$decorator = new FilteringDecorator($decorator, $filter);
}
return $decorator;
}
/**
* @param type $type
* @return DecoratorInterface
*/
protected function buildTypedDecorator($type)
{
switch($type) {
case 'inheritance':
return new InheritanceDecorator();
case 'interfaces':
return new InterfaceDecorator();
case 'traits':
return new TraitDecorator();
case 'attributes':
return new AttributeDecorator();
case 'methods':
return new MethodDecorator();
case 'entity':
return new EntityDecorator($this->entityManager->getMetadataFactory());
case 'associations':
return new AssociationDecorator($this->entityManager->getMetadataFactory());
case 'fields':
return new FieldDecorator($this->entityManager->getMetadataFactory());
default:
return NullDecorator::instance();
}
}
/**
* @param string $config
* @return NamespaceInterface
*/
protected function buildNamespace($config)
{
switch($config) {
case 'php':
return new RootNamespace();
case 'flat':
return new FlatNamespace();
case 'entities':
return new DoctrineNamespace($this->entityManager->getConfiguration()->getEntityNamespaces());
case 'bundles':
return new BundleNamespace($this->bundles);
}
}
/**
* @param array $config
* @return ClassFilterInterface|null
*/
protected function buildFilter(array $config)
{
$filters = array_merge(
isset($config['include']) ? $this->buildSubFilters($config['include'], false) : [],
isset($config['exclude']) ? $this->buildSubFilters($config['exclude'], true) : []
);
switch(count($filters)) {
case 0:
return null;
case 1:
return $filters[0];
default:
return new AllFilter($filters);
}
}
/**
* @param array $config
* @param boolean $notFound
* @return ClassFilterInterface|null
*/
protected function buildSubFilters(array $config, $notFound)
{
$filters = [];
if (!empty($config['directories'])) {
$paths = $this->parseDirectories($config['directories']);
$filters[] = new DirectoryFilter($paths, $notFound);
}
if (!empty($config['namespaces'])) {
$namespaces = $this->parseNamespaces($config['namespaces']);
$filters[] = new NamespaceFilter($namespaces, $notFound);
}
return $filters;
}
/**
* @param array $paths
* @return array
*/
protected function parseDirectories(array $paths)
{
$actualPaths = [];
foreach($paths as $path) {
if (preg_match('/^@(\w+)(.*)$/', $path, $groups)) {
$bundle = $this->kernel->getBundle($groups[1]);
$path = $bundle->getPath() . $groups[2];
}
$actualPaths[] = realpath($path);
}
return $actualPaths;
}
/**
* @param array $paths
* @return array
*/
protected function parseNamespaces(array $namespaces)
{
$actualNamespaces = [];
foreach($namespaces as $namespace) {
if (preg_match('/^@(\w+)(.*)$/', $namespace, $groups)) {
$bundle = $this->kernel->getBundle($groups[1]);
$namespace = $bundle->getNamespace() . $groups[2];
}
$actualNamespaces[] = $namespace;
}
return $actualNamespaces;
}
}
\ No newline at end of file
}
......@@ -8,8 +8,18 @@
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 Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
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
......@@ -20,10 +30,192 @@ class IrsteaPlantUmlExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('irstea_plant_uml.binaries', $config['binaries']);
$container->setParameter('irstea_plant_uml.graphs', $config['graphs']);
foreach($config['graphs'] as $key => $graph) {
$this->loadGraph($key, $graph, $container);
}
}
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);
}
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'))
->replaceArgument(0, $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"))
->replaceArgument(0, $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'))
->replaceArgument(0, $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))
->setPublic(false);
return new Reference($id);
}
}
......@@ -8,6 +8,7 @@
namespace Irstea\PlantUmlBundle\Doctrine;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Irstea\PlantUmlBundle\Model\ClassVisitorInterface;
......@@ -27,9 +28,9 @@ abstract class AbstractDoctrineDecorator implements DecoratorInterface
*/
protected $metadataFactory;
public function __construct(ClassMetadataFactory $metadataFactory)
public function __construct(EntityManagerInterface $manager)
{
$this->metadataFactory = $metadataFactory;
$this->metadataFactory = $manager->getMetadataFactory();
}
public function decorate(ReflectionClass $class, NodeInterface $node, ClassVisitorInterface $visitor)
......
......@@ -17,8 +17,10 @@ class DoctrineNamespace extends \Irstea\PlantUmlBundle\Model\Namespace_\MappedNa
{
const SEPARATOR = '::';
public function __construct(array $entityNamespaces)
public function __construct(\Doctrine\ORM\EntityManagerInterface $em)
{
$entityNamespaces = $em->getConfiguration()->getEntityNamespaces();
$mapping = [];
foreach($entityNamespaces as $alias => $namespace) {
$mapping[$namespace.'\\'] = $alias.'::';
......
<?php
/*
* © 2016 IRSTEA
* Guillaume Perréal <guillaume.perreal@irstea.fr>
* Tous droits réservés.
*/
namespace Irstea\PlantUmlBundle\Factory;
use Irstea\PlantUmlBundle\Model\ClassFilterInterface;
use Irstea\PlantUmlBundle\Model\Filter\AcceptAllFilter;
use Irstea\PlantUmlBundle\Model\Filter\Composite\AllFilter;
use Irstea\PlantUmlBundle\Model\Filter\DirectoryFilter;
use Irstea\PlantUmlBundle\Model\Filter\NamespaceFilter;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Description of FilterFactory
*
* @author Guillaume Perréal <guillaume.perreal@irstea.fr>
*/
class FilterFactory
{
/**
* @var KernelInterface
*/
private $kernel;
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
}
/**
* @param array $include
* @param array $exclude
* @return ClassFilterInterface
*/
public function create($include = [], $exclude = [])
{
$filters = [];
if (!empty($include)) {
$filters[] = $this->createSubFilters($include, false);
}
if (!empty($exclude)) {
$filters[] = $this->createSubFilters($exclude,