<?php /* * Copyright (C) 2015 IRSTEA * All rights reserved. */ 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\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\CompositeDecorator; use Irstea\PlantUmlBundle\Model\Decorator\FilteringDecorator; use Irstea\PlantUmlBundle\Model\Decorator\InheritanceDecorator; use Irstea\PlantUmlBundle\Model\Decorator\InterfaceDecorator; 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 * * @author Guillaume Perréal <guillaume.perreal@irstea.fr> */ class GenerateCommand extends ContainerAwareCommand { /** * @var string[] */ private $bundles; /** * @var KernelInterface */ private $kernel; /** * @var EntityManagerInterface */ private $entityManager; protected function configure() { $this ->setName('irstea:plantuml:generate') ->setDescription("Génère un graphe en PlantUML.") ->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 * @param OutputInterface $output * * @SuppressWarnings(UnusedFormalParameter) */ protected function execute(InputInterface $input, OutputInterface $output) { $name = $input->getArgument('graph'); $graphs = $this->getContainer()->getParameter('irstea_plant_uml.graphs'); if (!isset($graphs[$name])) { throw new InvalidArgumentException("Le graphe '$name' n'est pas défini."); } $config = $graphs[$name]; $graph = new Graph( new ClassVisitor( $this->buildDecorator($config['decoration']), $this->buildFilter($config['layout']), $this->buildNamespace($config['layout']['namespaces']) ), $this->buildFinder($config['sources']) ); $graph->visitAll(); $writer = new OutputWriter($output); $graph->outputTo($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; } return new FilteringFinder($finder, $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) { 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); 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 'entity': return new EntityDecorator($this->entityManager->getMetadataFactory()); case 'associations': return new AssociationDecorator($this->entityManager->getMetadataFactory()); } } /** * @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; } }