From 85f81393b5b5d5a60a32d9b144f5697837baf1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Perr=C3=A9al?= <guillaume.perreal@irstea.fr> Date: Wed, 9 Mar 2016 12:53:47 +0100 Subject: [PATCH] =?UTF-8?q?Mod=C3=A9lisation=20propres=20des=20namespaces?= =?UTF-8?q?=20et=20de=20writer.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Command/EntitySchemaCommand.php | 12 ++--- Model/AbstractNamespace.php | 91 +++++++++++++++++++++++++++++++++ Model/Arrow/BaseArrow.php | 28 +++++----- Model/ClassVisitor.php | 71 +++++++------------------ Model/NamespaceInterface.php | 34 ++++++++++++ Model/Namespace_.php | 68 ++++++++++++++++++++++++ Model/Node/BaseNode.php | 67 +++++++++++++++--------- Model/Orm/EntityVisitor.php | 2 + Model/RootNamespace.php | 45 ++++++++++++++++ Model/UmlArrowInterface.php | 17 ++++++ Model/UmlComponentInterface.php | 7 ++- Model/UmlNodeInterface.php | 9 +++- Writer/AbstractWriter.php | 66 ++++++++++++++++++++++++ Writer/OutputWriter.php | 34 ++++++++++++ Writer/WriterInterface.php | 41 +++++++++++++++ 15 files changed, 495 insertions(+), 97 deletions(-) create mode 100644 Model/AbstractNamespace.php create mode 100644 Model/NamespaceInterface.php create mode 100644 Model/Namespace_.php create mode 100644 Model/RootNamespace.php create mode 100644 Model/UmlArrowInterface.php create mode 100644 Writer/AbstractWriter.php create mode 100644 Writer/OutputWriter.php create mode 100644 Writer/WriterInterface.php diff --git a/Command/EntitySchemaCommand.php b/Command/EntitySchemaCommand.php index 3922e7c..2e86adf 100644 --- a/Command/EntitySchemaCommand.php +++ b/Command/EntitySchemaCommand.php @@ -8,7 +8,9 @@ namespace Irstea\PlantUmlBundle\Command; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; +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; @@ -50,11 +52,9 @@ class EntitySchemaCommand extends ContainerAwareCommand $visitor->visitClass($metadata->getName()); } - $stream = fopen("php://output", "wt"); - fputs($stream, "@startuml\n"); - fputs($stream, "set namespaceSeparator .\n"); - $visitor->outputTo($stream); - fputs($stream, "@enduml\n"); - fclose($stream); + $writer = new OutputWriter($output); + $writer->write("@startuml\n"); + $visitor->outputTo($writer); + $writer->write("@enduml\n"); } } diff --git a/Model/AbstractNamespace.php b/Model/AbstractNamespace.php new file mode 100644 index 0000000..783846d --- /dev/null +++ b/Model/AbstractNamespace.php @@ -0,0 +1,91 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model; + +use Irstea\PlantUmlBundle\Writer\WriterInterface; + +/** + * Description of Namespace + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +abstract class AbstractNamespace implements UmlComponentInterface, NamespaceInterface +{ + /** + * @var UmlNodeInterface[] + */ + private $nodes = []; + + /** + * @var NamespaceInterface + */ + private $children = []; + + /** + * + * @param UmlNodeInterface $node + */ + public function addNode(UmlNodeInterface $node) + { + $this->nodes[] = $node; + return $this; + } + + /** + * @param type $namespaceName + * @return NamespaceInterface + */ + public function getNamespace($namespaceName) + { + $namespaceName = trim($namespaceName, '\\'); + if (!$namespaceName) { + return $this; + } + @list($head, $tail) = explode('\\', $namespaceName, 2); + if (!isset($this->children[$head])) { + $this->children[$head] = new Namespace_($this, $head); + } + if (empty($tail)) { + return $this->children[$head]; + } + return $this->children[$head]->getNamespace($tail); + } + + /** + * @param WriterInterface $writer + * @return self + */ + protected function outputNodesTo(WriterInterface $writer) + { + foreach ($this->nodes as $node) { + $node->outputTo($writer); + } + return $this; + } + + /** + * @param WriterInterface $writer + * @return self + */ + protected function outputChildrenTo(WriterInterface $writer) + { + foreach ($this->children as $child) { + $child->outputTo($writer); + } + return $this; + } + + /** + * @return bool + */ + protected function isEmpty() + { + return empty($this->children) && empty($this->nodes); + } +} diff --git a/Model/Arrow/BaseArrow.php b/Model/Arrow/BaseArrow.php index 432ffe6..5ba6bc8 100644 --- a/Model/Arrow/BaseArrow.php +++ b/Model/Arrow/BaseArrow.php @@ -8,15 +8,16 @@ namespace Irstea\PlantUmlBundle\Model\Arrow; -use Irstea\PlantUmlBundle\Model\UmlComponentInterface; +use Irstea\PlantUmlBundle\Model\UmlArrowInterface; use Irstea\PlantUmlBundle\Model\UmlNodeInterface; +use Irstea\PlantUmlBundle\Writer\WriterInterface; /** * Description of Arrow * * @author Guillaume Perréal <guillaume.perreal@irstea.fr> */ -class BaseArrow implements UmlComponentInterface +class BaseArrow implements UmlArrowInterface { /** * @var UmlNodeInterface @@ -41,24 +42,27 @@ class BaseArrow implements UmlComponentInterface $this->label = $label; } - public function outputTo($stream) + public function outputTo(WriterInterface $writer) { - $this->source->outputAliasTo($stream); - $this->outputLinkTo($stream); - $this->target->outputAliasTo($stream); - $this->outputLabelTo($stream); - fputs($stream, "\n"); + $this->source->outputAliasTo($writer); + $this->outputLinkTo($writer); + $this->target->outputAliasTo($writer); + $this->outputLabelTo($writer); + $writer->write("\n"); + return $this; } - protected function outputLabelTo($stream) + protected function outputLabelTo(WriterInterface $writer) { if ($this->label) { - fputs($stream, " : {$this->label}"); + $writer->printf(" : %s", $this->label); } + return $this; } - protected function outputLinkTo($stream) + protected function outputLinkTo(WriterInterface $writer) { - fputs($stream, " ".$this->link." "); + $writer->printf(" %s ", $this->link); + return $this; } } diff --git a/Model/ClassVisitor.php b/Model/ClassVisitor.php index 4454140..08553b6 100644 --- a/Model/ClassVisitor.php +++ b/Model/ClassVisitor.php @@ -11,6 +11,7 @@ namespace Irstea\PlantUmlBundle\Model; use Irstea\PlantUmlBundle\Model\Node\Class_; use Irstea\PlantUmlBundle\Model\Node\Interface_; use Irstea\PlantUmlBundle\Model\Node\Trait_; +use Irstea\PlantUmlBundle\Writer\WriterInterface; use ReflectionClass; /** @@ -21,19 +22,14 @@ use ReflectionClass; class ClassVisitor implements ClassVisitorInterface, UmlComponentInterface { /** - * @var UmlNodeInterface[] + * @var RootNamespace */ - protected $nodes = []; + protected $rootNamespace; - /** - * @var UmlComponentInterface[] - */ - protected $arrows = []; - - /** - * @var string[] - */ - protected $namespaces = []; + public function __construct() + { + $this->rootNamespace = new RootNamespace(); + } public function visitClass($className) { @@ -69,8 +65,11 @@ class ClassVisitor implements ClassVisitorInterface, UmlComponentInterface } else { $node = new Class_($class->getName(), $class->isAbstract(), $class->isFinal()); } + $this->nodes[$class->getName()] = $node; - $this->visitNamespace($class->getNamespaceName()); + + $namespace = $this->rootNamespace->getNamespace($class->getNamespaceName()); + $namespace->addNode($node); $parentClass = $class->getParentClass(); $traitNames = $class->getTraitNames(); @@ -90,59 +89,25 @@ class ClassVisitor implements ClassVisitorInterface, UmlComponentInterface 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, $namespace, [$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->visitRelations($node, $namespace, $interfaceNames, 'Irstea\PlantUmlBundle\Model\Arrow\ImplementsInterface'); + $this->visitRelations($node, $namespace, $traitNames, 'Irstea\PlantUmlBundle\Model\Arrow\UsesTrait'); return $node; } - protected function visitRelations(UmlNodeInterface $source, array $classNames, $relationClass) + protected function visitRelations(UmlNodeInterface $source, NamespaceInterface $namespace, array $classNames, $relationClass) { foreach ($classNames as $className) { $target = $this->visitClass($className); - $this->arrows[] = new $relationClass($source, $target); + $namespace->addArrow(new $relationClass($source, $target)); } } - protected function visitNamespace($namespaceName) + public function outputTo(WriterInterface $writer) { - $current =& $this->namespaces; - foreach(explode('\\', $namespaceName) as $part) { - if (!isset($current[$part])) { - $current[$part] = []; - } - $current = &$current[$part]; - } - } - - public function outputTo($stream) - { - $this->outputNamespacesTo($stream, $this->namespaces); - - usort($this->nodes, 'Irstea\PlantUmlBundle\Model\Node\BaseNode::compare'); - foreach ($this->nodes as $node) { - $node->outputTo($stream); - } - - foreach ($this->arrows as $arrow) { - $arrow->outputTo($stream); - } - } - - protected function outputNamespacesTo($stream, $namespaces) - { - foreach($namespaces as $name => $children) { - fputs($stream, "namespace "); - /*while(count($children) == 1) { - fputs($stream, $name.'.'); - list($name, $children) = each($children); - }*/ - fputs($stream, "$name {\n"); - $this->outputNamespacesTo($stream, $children); - fputs($stream, "}\n"); - } + return $this->rootNamespace->outputTo($writer); } } diff --git a/Model/NamespaceInterface.php b/Model/NamespaceInterface.php new file mode 100644 index 0000000..1a1e01a --- /dev/null +++ b/Model/NamespaceInterface.php @@ -0,0 +1,34 @@ +<?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 NamespaceInterface +{ + /** + * @param string $namespaceName + * @return NamespaceInterface + */ + public function getNamespace($namespaceName); + + /** + * @param UmlNodeInterface $node + * @return self + */ + public function addNode(UmlNodeInterface $node); + + /** + * @param UmlArrowInterface $arrow + * @return self + */ + public function addArrow(UmlArrowInterface $arrow); +} diff --git a/Model/Namespace_.php b/Model/Namespace_.php new file mode 100644 index 0000000..09d4f32 --- /dev/null +++ b/Model/Namespace_.php @@ -0,0 +1,68 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model; + +use Irstea\PlantUmlBundle\Writer\WriterInterface; + +/** + * Description of Namespace + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class Namespace_ extends AbstractNamespace +{ + /** + * @var NamespaceInterface + */ + private $parent; + + /** + * @var string + */ + private $name; + + public function __construct(NamespaceInterface $parent, $name) + { + $this->parent = $parent; + $this->name = $name; + } + + /** + * @param UmlArrowInterface $arrow + * @return self + */ + public function addArrow(UmlArrowInterface $arrow) + { + $this->parent->addArrow($arrow); + return $this; + } + + /** + * @param resource WriterInterface $writer + * @return self + */ + public function outputTo(WriterInterface $writer) + { + if ($this->isEmpty()) { + return; + } + + $writer + ->printf("namespace %s {\n", $this->name) + ->indent(); + $this + ->outputNodesTo($writer) + ->outputChildrenTo($writer); + $writer + ->dedent() + ->write("}\n"); + + return $this; + } +} diff --git a/Model/Node/BaseNode.php b/Model/Node/BaseNode.php index 6ad1d0c..5c766dc 100644 --- a/Model/Node/BaseNode.php +++ b/Model/Node/BaseNode.php @@ -9,6 +9,7 @@ namespace Irstea\PlantUmlBundle\Model\Node; use Irstea\PlantUmlBundle\Model\UmlNodeInterface; +use Irstea\PlantUmlBundle\Writer\WriterInterface; /** * Description of Class @@ -22,6 +23,11 @@ class BaseNode implements UmlNodeInterface */ private $name; + /** + * @var string + */ + private $alias; + /** * @var string */ @@ -50,57 +56,72 @@ class BaseNode implements UmlNodeInterface */ function __construct($name, $nodeType, array $classifiers = [], array $stereotypes = [], $ordering = 0) { - $this->name = $name; + $pos = strrpos($name, '\\'); + $this->name = substr($name, $pos + 1); + $this->alias = str_replace('\\', '.', $name)."_node"; + $this->nodeType = $nodeType; $this->classifiers = $classifiers; $this->stereotypes = $stereotypes; $this->ordering = $ordering; } - public function outputTo($stream) + public function outputTo(WriterInterface $writer) { - $this->outputClassifiersTo($stream); - $this->outputNodeTypeTo($stream); - $this->outputAliasTo($stream); - $this->outputStereotypesTo($stream); - fputs($stream, " {\n"); - $this->outputAttributesTo($stream); - $this->outputMethodsTo($stream); - fputs($stream, "}\n"); + $this + ->outputClassifiersTo($writer) + ->outputNodeTypeTo($writer); + $writer->printf('"%s" as %s', $this->name, $this->alias); + $this + ->outputStereotypesTo($writer); + $writer + ->write(" {\n") + ->indent(); + $this + ->outputAttributesTo($writer) + ->outputMethodsTo($writer); + $writer + ->dedent() + ->write("}\n"); + return $this; } - public function outputAliasTo($stream) + public function outputAliasTo(WriterInterface $writer) { - fputs($stream, '"'.str_replace('\\', '.', $this->name).'"'); + $writer->write($this->alias); + return $this; } - protected function outputClassifiersTo($stream) + protected function outputClassifiersTo(WriterInterface $writer) { foreach($this->classifiers as $classifier) { - fputs($stream, "$classifier "); + $writer->printf('%s ', $classifier); } + return $this; } - protected function outputNodeTypeTo($stream) + protected function outputNodeTypeTo(WriterInterface $writer) { - fputs($stream, $this->nodeType." "); + $writer->printf('%s ', $this->nodeType); + return $this; } - protected function outputStereotypesTo($stream) + protected function outputStereotypesTo(WriterInterface $writer) { - foreach($this->stereotypes as $stereotypes) { - fputs($stream, " <<$stereotypes>>"); + foreach($this->stereotypes as $stereotype) { + $writer->printf(' <<%s>>', $stereotype); } + return $this; } - protected function outputAttributesTo($stream) + protected function outputAttributesTo(WriterInterface $writer) { - // NOP + return $this; } - protected function outputMethodsTo($stream) + protected function outputMethodsTo(WriterInterface $writer) { - // NOP + return $this; } static public function compare(BaseNode $a, BaseNode $b) diff --git a/Model/Orm/EntityVisitor.php b/Model/Orm/EntityVisitor.php index 7478637..9830995 100644 --- a/Model/Orm/EntityVisitor.php +++ b/Model/Orm/EntityVisitor.php @@ -25,6 +25,8 @@ class EntityVisitor extends ClassVisitor public function __construct(array $metadata) { + parent::__construct(); + foreach($metadata as $md) { /* @var $md ClassMetadata */ $this->metadata[$md->getReflectionClass()->getName()] = $md; diff --git a/Model/RootNamespace.php b/Model/RootNamespace.php new file mode 100644 index 0000000..968af9d --- /dev/null +++ b/Model/RootNamespace.php @@ -0,0 +1,45 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Model; + +/** + * Description of RootNamespace + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class RootNamespace extends AbstractNamespace +{ + /** + * @var UmlArrowInterface[] + */ + private $arrows = []; + + public function addArrow(UmlArrowInterface $arrow) + { + $this->arrows[] = $arrow; + return $this; + } + + public function outputTo(\Irstea\PlantUmlBundle\Writer\WriterInterface $writer) + { + $this + ->outputNodesTo($writer) + ->outputChildrenTo($writer) + ->outputArrowsTo($writer); + return $this; + } + + protected function outputArrowsTo(\Irstea\PlantUmlBundle\Writer\WriterInterface $writer) + { + foreach ($this->arrows as $arrow) { + $arrow->outputTo($writer); + } + return $this; + } +} diff --git a/Model/UmlArrowInterface.php b/Model/UmlArrowInterface.php new file mode 100644 index 0000000..9bc1d02 --- /dev/null +++ b/Model/UmlArrowInterface.php @@ -0,0 +1,17 @@ +<?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 UmlArrowInterface extends UmlComponentInterface +{ +} diff --git a/Model/UmlComponentInterface.php b/Model/UmlComponentInterface.php index 65b13c7..1089443 100644 --- a/Model/UmlComponentInterface.php +++ b/Model/UmlComponentInterface.php @@ -8,11 +8,16 @@ namespace Irstea\PlantUmlBundle\Model; +use Irstea\PlantUmlBundle\Writer\WriterInterface; + /** * * @author Guillaume Perréal <guillaume.perreal@irstea.fr> */ interface UmlComponentInterface { - public function outputTo($stream); + /** + * @param WriterInterface $writer + */ + public function outputTo(WriterInterface $writer); } diff --git a/Model/UmlNodeInterface.php b/Model/UmlNodeInterface.php index 2594948..8ffed94 100644 --- a/Model/UmlNodeInterface.php +++ b/Model/UmlNodeInterface.php @@ -8,11 +8,16 @@ namespace Irstea\PlantUmlBundle\Model; +use Irstea\PlantUmlBundle\Writer\WriterInterface; + /** * * @author Guillaume Perréal <guillaume.perreal@irstea.fr> */ -interface UmlNodeInterface extends \Irstea\PlantUmlBundle\Model\UmlComponentInterface +interface UmlNodeInterface extends UmlComponentInterface { - public function outputAliasTo($stream); + /** + * @param WriterInterface $writer + */ + public function outputAliasTo(WriterInterface $writer); } diff --git a/Writer/AbstractWriter.php b/Writer/AbstractWriter.php new file mode 100644 index 0000000..7a14d3a --- /dev/null +++ b/Writer/AbstractWriter.php @@ -0,0 +1,66 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Writer; + +/** + * Description of AbstractWriter + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +abstract class AbstractWriter implements WriterInterface +{ + /** + * @var int + */ + private $indentation = 0; + + /** + * @var string + */ + private $buffer; + + public function dedent($n = 1) + { + $this->indentation = max(0, $this->indentation - $n); + return $this; + } + + public function indent($n = 1) + { + $this->indentation += $n; + return $this; + } + + public function printf($format) + { + $args = func_get_args(); + array_shift($args); + $this->write(vsprintf($format, $args)); + return $this; + } + + public function write($data) + { + $buffer = $this->buffer . $data; + $indentation = str_repeat(" ", $this->indentation); + $current = 0; + while($current < strlen($buffer) && false !== ($next = strpos($buffer, "\n", $current))) { + $next++; + $this->doWrite($indentation . substr($buffer, $current, $next - $current)); + $current = $next; + } + $this->buffer = substr($buffer, $current); + return $this; + } + + /** + * @param string $data + */ + abstract protected function doWrite($data); +} diff --git a/Writer/OutputWriter.php b/Writer/OutputWriter.php new file mode 100644 index 0000000..c43fb7f --- /dev/null +++ b/Writer/OutputWriter.php @@ -0,0 +1,34 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Writer; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Description of OutputWriter + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +class OutputWriter extends AbstractWriter +{ + /** + * @var OutputInterface + */ + private $output; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + protected function doWrite($data) + { + $this->output->write($data); + } +} diff --git a/Writer/WriterInterface.php b/Writer/WriterInterface.php new file mode 100644 index 0000000..06036fa --- /dev/null +++ b/Writer/WriterInterface.php @@ -0,0 +1,41 @@ +<?php + +/* + * © 2016 IRSTEA + * Guillaume Perréal <guillaume.perreal@irstea.fr> + * Tous droits réservés. + */ + +namespace Irstea\PlantUmlBundle\Writer; + +/** + * + * @author Guillaume Perréal <guillaume.perreal@irstea.fr> + */ +interface WriterInterface +{ + /** + * @param int $n + * @return self + */ + public function indent($n = 1); + + /** + * @param int $n + * @return self + */ + public function dedent($n = 1); + + /** + * @param string $data + * @return self + */ + public function write($data); + + /** + * @param string $fmt + * @param mixed ... + * @return self + */ + public function printf($fmt); +} -- GitLab