diff --git a/Command/EntitySchemaCommand.php b/Command/EntitySchemaCommand.php index e290e0a5e9e1c903242cdd30c645bae9314231ae..be44af3bf365debb205c4c7aaf00372c4731ba33 100644 --- a/Command/EntitySchemaCommand.php +++ b/Command/EntitySchemaCommand.php @@ -8,16 +8,18 @@ namespace Irstea\PlantUmlBundle\Command; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; +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\NullDecorator; use Irstea\PlantUmlBundle\Model\Filter\Whitelist; -use Irstea\PlantUmlBundle\Model\Namespace_; -use Irstea\PlantUmlBundle\Model\Orm\EntityVisitor; use Irstea\PlantUmlBundle\Writer\OutputWriter; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; + /** * Description of ImportAffiliationCommand * @@ -52,7 +54,13 @@ class EntitySchemaCommand extends ContainerAwareCommand $allMetadata ); - $visitor = new EntityVisitor($allMetadata); + $decorator = new FilteringDecorator( + new InheritanceDecorator(), + new Whitelist($classes) + ); + + $visitor = new ClassVisitor($decorator); + array_walk($classes, [$visitor, 'visitClass']); $writer = new OutputWriter($output); diff --git a/Model/ClassVisitor.php b/Model/ClassVisitor.php index 0ee2cba7d8aafd3a80603e23aebccab2e059ba13..d39a4f47b5335c2e9d3577bdaaca612d5d0b05c3 100644 --- a/Model/ClassVisitor.php +++ b/Model/ClassVisitor.php @@ -35,10 +35,16 @@ class ClassVisitor implements ClassVisitorInterface, WritableInterface */ protected $filter; - public function __construct(ClassFilterInterface $filter = null) + /** + * @var DecoratorInterface + */ + protected $decorator; + + public function __construct(DecoratorInterface $decorator = null, ClassFilterInterface $filter = null) { $this->rootNamespace = new RootNamespace(); $this->filter = $filter ?: AcceptAllFilter::instance(); + $this->decorator = $decorator ?: NullDecorator::instance(); } /** @@ -51,6 +57,16 @@ class ClassVisitor implements ClassVisitorInterface, WritableInterface return $this; } + /** + * @param DecoratorInterface $decorator + * @return self + */ + public function setDecorator(DecoratorInterface $decorator) + { + $this->decorator = $decorator; + return $this; + } + public function visitClass($className) { assert('is_string($className)', $className); @@ -61,7 +77,7 @@ class ClassVisitor implements ClassVisitorInterface, WritableInterface return $this->nodes[$className] = false; } - return $this->doVisitClass($className); + return $this->createNode($className); } /** @@ -69,68 +85,27 @@ class ClassVisitor implements ClassVisitorInterface, WritableInterface * @param string $className * @return NodeInterface */ - protected function doVisitClass($className) - { - $reflection = new ReflectionClass($className); - return $this->visitClassReflection($reflection); - } - - /** - * @param ReflectionClass $class - * @return NodeInterface - */ - protected function visitClassReflection(ReflectionClass $class) + protected function createNode($className) { + $class = new ReflectionClass($className); $namespace = $this->rootNamespace->getNamespace($class->getNamespaceName()); if ($class->isTrait()) { - $node = new Trait_($namespace, $class->getName()); + $node = new Trait_($namespace, $className); } elseif ($class->isInterface()) { - $node = new Interface_($namespace, $class->getName()); + $node = new Interface_($namespace, $className); } else { - $node = new Class_($namespace, $class->getName(), $class->isAbstract(), $class->isFinal()); + $node = new Class_($namespace, $className, $class->isAbstract(), $class->isFinal()); } $this->nodes[$class->getName()] = $node; $namespace->addNode($node); - $parentClass = $class->getParentClass(); - $traitNames = $class->getTraitNames(); - - $indirectInterfaces = array_filter( - array_map( - function ($i) { return $i->getInterfaceNames(); }, - $class->getInterfaces() - ) - ); - $interfaceNames = $class->getInterfaceNames(); - if (!empty($indirectInterfaces)) { - $indirectInterfaces = call_user_func_array('array_merge', $indirectInterfaces); - $interfaceNames = array_diff($interfaceNames, $indirectInterfaces); - } - - if ($parentClass) { - $traitNames = array_diff($traitNames, $parentClass->getTraitNames()); - $interfaceNames = array_diff($interfaceNames, $parentClass->getInterfaceNames()); - $this->visitRelations($node, [$parentClass->getName()], 'Irstea\PlantUmlBundle\Model\Arrow\ExtendsClass'); - } - - $this->visitRelations($node, $interfaceNames, 'Irstea\PlantUmlBundle\Model\Arrow\ImplementsInterface'); - $this->visitRelations($node, $traitNames, 'Irstea\PlantUmlBundle\Model\Arrow\UsesTrait'); + $this->decorator->decorate($className, $node, $this); return $node; } - protected function visitRelations(NodeInterface $source, array $classNames, $relationClass) - { - foreach ($classNames as $className) { - $target = $this->visitClass($className); - if ($target) { - $source->addArrow(new $relationClass($source, $target)); - } - } - } - public function outputTo(WriterInterface $writer) { return $this->rootNamespace->outputTo($writer); diff --git a/Model/Decorator/CompositeDecorator.php b/Model/Decorator/CompositeDecorator.php new file mode 100644 index 0000000000000000000000000000000000000000..788210a9c0e7d664b53d7b2541f5581da10ce7bb --- /dev/null +++ b/Model/Decorator/CompositeDecorator.php @@ -0,0 +1,45 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model\Decorator; + +use Irstea\PlantUmlBundle\Model\ClassVisitorInterface; +use Irstea\PlantUmlBundle\Model\DecoratorInterface; +use Irstea\PlantUmlBundle\Model\NodeInterface; + +/** + * Description of CompositeDecorator + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class CompositeDecorator implements DecoratorInterface +{ + /** + * @var DecoratorInterface[] + */ + private $decorators = []; + + public function __construct(array $decorators = []) + { + $this->decorators = $decorators; + } + + public function addDecorator(DecoratorInterface $decorator) + { + $this->decorators[] = $decorator; + return $this; + } + + public function decorate($className, NodeInterface $node, ClassVisitorInterface $visitor) + { + foreach($this->decorators as $decorator) { + $decorator->decorate($className, $node, $visitor); + } + return $this; + } +} diff --git a/Model/Decorator/FilteringDecorator.php b/Model/Decorator/FilteringDecorator.php new file mode 100644 index 0000000000000000000000000000000000000000..6025de1d5b76d21be1277083c52da69cf2f7160c --- /dev/null +++ b/Model/Decorator/FilteringDecorator.php @@ -0,0 +1,50 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model\Decorator; + +use Irstea\PlantUmlBundle\Model\ClassFilterInterface; +use Irstea\PlantUmlBundle\Model\ClassVisitorInterface; +use Irstea\PlantUmlBundle\Model\DecoratorInterface; +use Irstea\PlantUmlBundle\Model\NodeInterface; + +/** + * Description of FilteringDecorator + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class FilteringDecorator implements DecoratorInterface +{ + /** + * @var DecoratorInterface + */ + private $next; + + /** + * @var ClassFilterInterface + */ + private $filter; + + /** + * @param DecoratorInterface $next + * @param ClassFilterInterface $filter + */ + public function __construct(DecoratorInterface $next, ClassFilterInterface $filter) + { + $this->next = $next; + $this->filter = $filter; + } + + public function decorate($className, NodeInterface $node, ClassVisitorInterface $visitor) + { + if ($this->filter->accept($className)) { + $this->next->decorate($className, $node, $visitor); + } + return $this; + } +} diff --git a/Model/Decorator/InheritanceDecorator.php b/Model/Decorator/InheritanceDecorator.php new file mode 100644 index 0000000000000000000000000000000000000000..d3b52c462295aa727f517af0061c212884866901 --- /dev/null +++ b/Model/Decorator/InheritanceDecorator.php @@ -0,0 +1,65 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model\Decorator; + +use Irstea\PlantUmlBundle\Model\ClassVisitor; +use Irstea\PlantUmlBundle\Model\ClassVisitorInterface; +use Irstea\PlantUmlBundle\Model\DecoratorInterface; +use Irstea\PlantUmlBundle\Model\NodeInterface; +use ReflectionClass; + +/** + * Description of InheritanceDecorator + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class InheritanceDecorator implements DecoratorInterface +{ + public function decorate($className, NodeInterface $node, ClassVisitorInterface $visitor) + { + $class = new ReflectionClass($className); + + $parent = $class->getParentClass(); + $traits = $class->getTraitNames(); + $interfaces = $class->getInterfaceNames(); + + $indirectInterfaces = array_filter( + array_map( + function ($i) { return $i->getInterfaceNames(); }, + $class->getInterfaces() + ) + ); + + if (!empty($indirectInterfaces)) { + $indirectInterfaces = call_user_func_array('array_merge', $indirectInterfaces); + $interfaces = array_diff($interfaces, $indirectInterfaces); + } + + if ($parent) { + $traits = array_diff($traits, $parent->getTraitNames()); + $interfaces = array_diff($interfaces, $parent->getInterfaceNames()); + $this->visitRelations($node, [$parent->getName()], 'Irstea\PlantUmlBundle\Model\Arrow\ExtendsClass', $visitor); + } + + $this->visitRelations($node, $interfaces, 'Irstea\PlantUmlBundle\Model\Arrow\ImplementsInterface', $visitor); + $this->visitRelations($node, $traits, 'Irstea\PlantUmlBundle\Model\Arrow\UsesTrait', $visitor); + + return $this; + } + + protected function visitRelations(NodeInterface $source, array $classNames, $relationClass, ClassVisitorInterface $visitor) + { + foreach ($classNames as $className) { + $target = $visitor->visitClass($className); + if ($target) { + $source->addArrow(new $relationClass($source, $target)); + } + } + } +} diff --git a/Model/Decorator/NullDecorator.php b/Model/Decorator/NullDecorator.php new file mode 100644 index 0000000000000000000000000000000000000000..e55976fac5eb8177b534ac808441700f5fb5f59d --- /dev/null +++ b/Model/Decorator/NullDecorator.php @@ -0,0 +1,28 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model\Decorator; + +use Irstea\PlantUmlBundle\Model\ClassVisitorInterface; +use Irstea\PlantUmlBundle\Model\DecoratorInterface; +use Irstea\PlantUmlBundle\Model\NodeInterface; + +/** + * Description of NullDecorator + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class NullDecorator implements DecoratorInterface +{ + use \Irstea\PlantUmlBundle\Utils\Singleton; + + public function decorate($className, NodeInterface $node, ClassVisitorInterface $visitor) + { + return $this; + } +} diff --git a/Model/DecoratorInterface.php b/Model/DecoratorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..72c530bc7d3fe6e9f3dc056b1cf41bc51cd8df23 --- /dev/null +++ b/Model/DecoratorInterface.php @@ -0,0 +1,18 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model; + +/** + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +interface DecoratorInterface +{ + public function decorate($className, NodeInterface $node, ClassVisitorInterface $visitor); +} diff --git a/Model/Filter/AcceptAllFilter.php b/Model/Filter/AcceptAllFilter.php index 287052de9556c791a80d93c087b0b0d0dedccd86..7ec74eec90f4d6628db0c4ea587afaeab41f5c46 100644 --- a/Model/Filter/AcceptAllFilter.php +++ b/Model/Filter/AcceptAllFilter.php @@ -17,24 +17,10 @@ use Irstea\PlantUmlBundle\Model\ClassFilterInterface; */ class AcceptAllFilter implements ClassFilterInterface { + use \Irstea\PlantUmlBundle\Utils\Singleton; + public function accept($className) { return true; } - - /** - * @var self - */ - static private $instance; - - /** - * @return self - */ - static public function instance() - { - if (!static::$instance) { - static::$instance = new static(); - } - return static::$instance; - } } diff --git a/Model/Orm/EntityVisitor.php b/Model/Orm/EntityVisitor.php deleted file mode 100644 index 48342e1104d742281cd6348f137601c56879f9df..0000000000000000000000000000000000000000 --- a/Model/Orm/EntityVisitor.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -/* - * © 2016 IRSTEA - * Guillaume Perréal <guillaume.perreal@irstea.fr> - * Tous droits réservés. - */ - -namespace Irstea\PlantUmlBundle\Model\Orm; - -use Doctrine\ORM\Mapping\ClassMetadata; -use Irstea\PlantUmlBundle\Model\ClassVisitor; - -/** - * Description of Visitor - * - * @author Guillaume Perréal <guillaume.perreal@irstea.fr> - */ -class EntityVisitor extends ClassVisitor -{ - /** - * @var ClassMetadata[] - */ - protected $metadata = []; - - public function __construct(array $metadata, \Irstea\PlantUmlBundle\Model\ClassFilterInterface $filter = null) - { - parent::__construct($filter); - - foreach($metadata as $md) { - /* @var $md ClassMetadata */ - $this->metadata[$md->getReflectionClass()->getName()] = $md; - } - } - - public function doVisitClass($className) - { - if (isset($this->metadata[$className])) { - return $this->visitClassMetadata($this->metadata[$className]); - } - - return parent::doVisitClass($className); - } - - /** - * @param ClassMetadata $metadata - * @return AbstractNode - */ - protected function visitClassMetadata(ClassMetadata $metadata) - { - return $this->visitClassReflection($metadata->getReflectionClass()); - } -} diff --git a/Utils/Singleton.php b/Utils/Singleton.php new file mode 100644 index 0000000000000000000000000000000000000000..d09a44e1fd7c48f95cadfaf9d4a62345d73c91a0 --- /dev/null +++ b/Utils/Singleton.php @@ -0,0 +1,33 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Utils; + +/** + * Description of Singleton + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +trait Singleton +{ + /** + * @var self + */ + static private $instance; + + /** + * @return self + */ + static public function instance() + { + if (!static::$instance) { + static::$instance = new static(); + } + return static::$instance; + } +}