diff --git a/Command/EntitySchemaCommand.php b/Command/EntitySchemaCommand.php index 3922e7c83accfb67fbe4e0f370a8f9370581cfaa..2e86adf347863fd6749dd4753a9c6b01de03c238 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 0000000000000000000000000000000000000000..783846d83d343b70bf450c01b72a26d0c29a89a9 --- /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 432ffe631f6694b254adcde6ebb965b7935970a9..5ba6bc8ac7976ff5a28188e5ff3b10756e8ce33b 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 4454140d4482f2d5e21745fd68610609f0905fa3..08553b628184db102213880602a5ecf04120081a 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 0000000000000000000000000000000000000000..1a1e01a96172eecc0d616aeda7d5e16971f690ff --- /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 0000000000000000000000000000000000000000..09d4f326ec8d5467a203ae612aea2dcc55ec1046 --- /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 6ad1d0c62b30355cb604f27ed97145899491592e..5c766dc54baf7848170b43c17d9fdbcb24f6ee7a 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 74786379d83ad55bdf43bc254b6fdb184ba29d64..9830995a04d1067ae39bd56550a4bc53dfa496e1 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 0000000000000000000000000000000000000000..968af9d5a70190aea6a18016841e3950f17617b9 --- /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 0000000000000000000000000000000000000000..9bc1d028c59a7f33f6931b95d13ae12123552150 --- /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 65b13c7c9a726f136343582ff99f523a4fd4d4a2..1089443982dde26122946cccb16788823e326eb9 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 2594948c2e1b514c08c20a6cc57f7c31ea0baceb..8ffed94865aa08de34c27ebe517b703d9e518fa7 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 0000000000000000000000000000000000000000..7a14d3a2f40401feb7ba39506b1a8f556e41771a --- /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 0000000000000000000000000000000000000000..c43fb7fe997ffd98b5a266f2271f0533975e145d --- /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 0000000000000000000000000000000000000000..06036fa3c2175b09cf07de21033e01ce5c5281da --- /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); +}