From 051af78feb396b96489a65dd83d4c47e3f9b2364 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Guillaume=20Perr=C3=A9al?= <guillaume.perreal@irstea.fr>
Date: Wed, 9 Mar 2016 15:04:15 +0100
Subject: [PATCH] =?UTF-8?q?Mise=20en=20place=20d'un=20framework=20de=20d?=
 =?UTF-8?q?=C3=A9corateurs.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Command/EntitySchemaCommand.php          | 18 ++++--
 Model/ClassVisitor.php                   | 73 ++++++++----------------
 Model/Decorator/CompositeDecorator.php   | 45 +++++++++++++++
 Model/Decorator/FilteringDecorator.php   | 50 ++++++++++++++++
 Model/Decorator/InheritanceDecorator.php | 65 +++++++++++++++++++++
 Model/Decorator/NullDecorator.php        | 28 +++++++++
 Model/DecoratorInterface.php             | 18 ++++++
 Model/Filter/AcceptAllFilter.php         | 18 +-----
 Model/Orm/EntityVisitor.php              | 53 -----------------
 Utils/Singleton.php                      | 33 +++++++++++
 10 files changed, 278 insertions(+), 123 deletions(-)
 create mode 100644 Model/Decorator/CompositeDecorator.php
 create mode 100644 Model/Decorator/FilteringDecorator.php
 create mode 100644 Model/Decorator/InheritanceDecorator.php
 create mode 100644 Model/Decorator/NullDecorator.php
 create mode 100644 Model/DecoratorInterface.php
 delete mode 100644 Model/Orm/EntityVisitor.php
 create mode 100644 Utils/Singleton.php

diff --git a/Command/EntitySchemaCommand.php b/Command/EntitySchemaCommand.php
index e290e0a..be44af3 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 0ee2cba..d39a4f4 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 0000000..788210a
--- /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 0000000..6025de1
--- /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 0000000..d3b52c4
--- /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 0000000..e55976f
--- /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 0000000..72c530b
--- /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 287052d..7ec74ee 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 48342e1..0000000
--- 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 0000000..d09a44e
--- /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;
+    }
+}
-- 
GitLab