1. Make UML diagrams
We will create PlantUML diagrams corresponding to current source of Reflect architecture.
Reflect provides its results that can be exploited by external process by implementing the Visitor Pattern.
Our goal is to create a PlantUML builder class that will implement this design pattern, and can be able to produce code that make such diagram :
1.1. Parsing Data Source
As usual, we need to parse data source first before we can explore its elements.
<?php
use Bartlett\Reflect;
use Bartlett\Reflect\ProviderManager;
use Bartlett\Reflect\Provider\SymfonyFinderProvider;
use Symfony\Component\Finder\Finder;
$dirs = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src';
$finder = new Finder();
$finder->files()
->name('*.php')
->in($dirs);
$provider = new SymfonyFinderProvider($finder);
$pm = new ProviderManager;
$pm->set('ReflectSource', $provider);
$reflect = new Reflect;
$reflect->setProviderManager($pm);
$reflect->parse();
1.2. Visitors
The PlantUML builder need to know what is the contents of packages in the data source.
The builder class will extend the abstract class Bartlett\Reflect\Visitor\AbstractVisitor
,
that implement interface Bartlett\Reflect\Visitor\VisitorInterface
.
<?php
use Bartlett\Reflect\Visitor\AbstractVisitor;
class PlantUMLBuilder
extends AbstractVisitor
{
}
Each element that need to be explored, should have a visit method accordingly.
-
For packages, we need to implement a visitPackageModel method.
-
For classes, we need to implement a visitClassModel method, and so on …
<?php
use Bartlett\Reflect\Visitor\AbstractVisitor;
class PlantUMLBuilder
extends AbstractVisitor
{
protected $packages;
protected $pkgid;
protected $clsid;
public function visitPackageModel( $package )
{
$this->pkgid = md5($package->getName());
$this->packages[$this->pkgid] = array(
'name' => str_replace('\\', '\\\\', $package->getName()),
'classes' => array(),
);
foreach ($package->getClasses() as $class) {
$class->accept($this);
}
}
public function visitClassModel( $class )
{
if ($class->isAbstract()) {
$interface = 'abstract';
}
elseif ($class->isInterface()){
$interface = 'interface';
}
else {
$interface = 'class';
}
$this->clsid = md5($class->getName());
$this->packages[$this->pkgid]['classes'][$this->clsid] = array(
'type' => $interface,
'name' => $class->getShortName(),
);
}
}
1.3. Build the PlantUML code
Finally, after collecting all results (PlantUMLBuilder::__construct()
),
its now time to get code to write PlantUML diagrams (PlantUMLBuilder::getPackageDiagram()
).
<?php
use Bartlett\Reflect\Visitor\AbstractVisitor;
class PlantUMLBuilder
extends AbstractVisitor
{
protected $packages;
public function __construct( $reflect )
{
$this->packages = array();
// explore elements results of data source parsed
foreach ($reflect->getPackages() as $package) {
$package->accept($this);
}
}
public function getPackageDiagram()
{
$eol = "\n";
$diag = '';
// produce plantuml diagram
foreach($this->packages as $packageValues) {
$diag .= sprintf('package "%s" {%s',
$packageValues['name'],
$eol
);
foreach($packageValues['classes'] as $classValues) {
$diag .= sprintf('%s %s%s',
$classValues['type'],
$classValues['name'],
$eol
);
}
$diag .= sprintf('}%s',
$eol
);
}
return $diag;
}
}
1.4. Build images
With previous class, we get the PlantUML code that is write in a local file.
<?php
$plantuml = new PlantUMLBuilder( $reflect );
$diag = $plantuml->getPackageDiagram();
$fp = fopen(__DIR__ . DIRECTORY_SEPARATOR . 'packageDiagram.plantuml', 'w+');
fwrite($fp, $diag);
fclose($fp);
It’s now time to produce PNG images.
$ java -jar plantuml.jar packageDiagram.plantuml
The PlantUMLBuilder
class can be easily modified to produce
AsciiDoc PlantUML filter
compatible results, such as chunk of code below :
["plantuml"] --------------------------------------------------------------------- package "Bartlett\\Reflect\\Model" { abstract AbstractFunctionModel abstract AbstractModel class ClassModel class ConstantModel class FunctionModel class IncludeModel class MethodModel class NamespaceModel class PackageModel class ParameterModel class PropertyModel class VariableModel interface Visitable } ---------------------------------------------------------------------
1.5. And more again
<?php
class PlantUMLBuilder
extends AbstractVisitor
{
protected $asciidoc;
public function __construct( $reflect, $asciidoc_option = false )
{
$this->asciidoc = $asciidoc_option;
}
public function getPackageDiagram()
{
foreach($this->packages as $packageValues) {
if ($this->asciidoc) {
$diag .= '["plantuml"]' . $eol;
$diag .= '---------------------------------------------------------------------' . $eol;
}
// ... package data processing here
if ($this->asciidoc) {
$diag .= '---------------------------------------------------------------------' . $eol;
}
}
}
}
PlantUML can produce lot of diagrams. Now you know how to explore parsing results,
and make a package diagram, we will modify the PlantUMLBuilder
class to make this kind of class diagram :
The visitClassModel
need to explore methods.
<?php
class PlantUMLBuilder
extends AbstractVisitor
{
public function visitClassModel( $class )
{
// ...
foreach ($class->getMethods() as $method) {
$method->accept($this);
}
}
}
We must add the corresponding visitor visitMethodModel
.
<?php
class PlantUMLBuilder
extends AbstractVisitor
{
public function visitMethodModel( $method )
{
if ($method->isPrivate()) {
$visibility = '-';
} elseif ($method->isProtected()) {
$visibility = '#';
} else {
$visibility = '+';
}
$this->packages[$this->pkgid]['classes'][$this->clsid]['methods'][] = array(
'visibility' => $visibility,
'name' => $method->getShortName(),
);
}
}
And last but not least, we add the getClassDiagram
method that make the PlantUML code.
<?php
class PlantUMLBuilder
extends AbstractVisitor
{
public function getClassDiagram( $qualifiedClass )
{
$parts = explode('\\', $qualifiedClass);
$className = array_pop($parts);
$packageName = implode('\\', $parts);
$packageValues = $this->packages[ md5($packageName) ];
$classValues = $packageValues['classes'][ md5($qualifiedClass) ];
$eol = "\n";
$diag = '';
// produce plantuml diagram
if ($this->asciidoc) {
$diag .= '["plantuml"]' . $eol;
$diag .= '---------------------------------------------------------------------' . $eol;
}
$diag .= sprintf('%s %s{%s',
$classValues['type'],
$classValues['name'],
$eol
);
foreach($classValues['methods'] as $method) {
$diag .= sprintf(' %s%s()%s',
$method['visibility'],
$method['name'],
$eol
);
}
$diag .= sprintf('}%s',
$eol
);
if ($this->asciidoc) {
$diag .= '---------------------------------------------------------------------' . $eol;
}
return $diag;
}
}
PlantUML code is once more again written to a local file, before building image with java tools.
<?php
$plantuml = new PlantUMLBuilder( $reflect, true );
$diag = $plantuml->getClassDiagram( 'Bartlett\Reflect\Builder' );
$fp = fopen(__DIR__ . DIRECTORY_SEPARATOR . 'classDiagram.plantuml', 'w+');
fwrite($fp, $diag);
fclose($fp);
The full source code
[https://raw.github.com/llaville/php-reflect/v2/examples/PlantUMLBuilder.php]
is available on GitHub repository.