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 :

plantUML_packageDiag.png

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

Modifications to make it AsciiDoc PlantUML filter compatible
<?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 :

plantUML_classDiag.png

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.