Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 635 posts at DZone. You can read more from them at their website. View Full User Profile

PHP UML generation from a live object graph

05.10.2011
| 11125 views |
  • submit to reddit

Sometimes you need to share a design with your colleagues. You can walk him through the code, and explain which classes and interfaces you created, but there are higher abstracted models that you can show to him to make him grasp the picture quickly.

One of these tools is UML, and in particular class diagrams. A good class diagram can show the relationships between a dozen of classes, and thus the design of an entire small component.
I'm no advocate of designing software before coding however, since the code is the ultimate design document. When we prepare UML diagrams, we mostly are in the stage after having Test-Driven the code and committed it. The goal is sharing information quickly, and then throw away the diagram when it gets out of sync with the code.

How?

An approach to produce this kind of diagrams is to have one of the members of a pair programming duo draw on paper with a pencil and a rubber. Paper still is the best expressive medium, and pencil allows for easy editing. In some cases, we even update our paper sheet while we were extracting classes during refactoring phase.

Even when you draw Uml it with a computer-based tool, the boring part is drawing the diagram. After all, part of the information is already embedded in the code; and we already know that this diagram will be short-lived, since the code evolves quickly.

So as a proof of concept I started writing a tool, PHP UML Generator, which extracts an Uml class diagram from a PHP object graph, which has been previously instantiated from some factory. It's basic reverse engineering.

Existent tools like Doxygen produce diagrams of this kind, but starting from static analysis of the code, not from a live object graph. This means Umlntrospector would be more difficult to integrate, as you need to bootstrap your application, but I think this is outweighted by two factors:

  • the single source of truth is the object graph: when annotations are outdated or missing, if you have a working test suite the object graph will be in sync with the current state of the code.
  • the solution is simpler: Reflection is used to extract information instead of parsing of docblock comments and code.

Features

Since this is a proof of concept, I tried to build a minimum viable product:

  • by default the introspector produces UML definitions for Yuml.me, used for visualization. Other adapters can be added.
  • The introspector follows composition and inheritance to N levels: class relationships are more important than methods, which are also likely to be too many to show.
  • The introspector does not distinguish between objects of the same class for now (a class diagram is the objective, but they may be more associations, named differently.)
  • It's able to ignore libraries namespaces like Zend_ or Doctrine_.
  • It skips scalar fields as they provide almost no information.
  • It considers base class names and PHP 5.3 namespaces.

Conclusion

With this article I'm validating with the community the idea of analyzing a live object graph. Here are some very small examples of the results produces on PHPUnit objects and a Doctrine 2 Entity Manager with this code (cannot run standalone, it's included just for explanation):

<?php
require_once '/home/giorgio/code/pug/tests/bootstrap.php';
require_once '/home/giorgio/code/ddd-talk/bootstrap.php';

class UmlIntrospectorPHPUnitTest extends PHPUnit_Framework_TestCase
{
    public function testGenerationOfyUMLCode()
    {
        $introspector = new UmlReflector\Introspector;
        $directives = new UmlReflector\Directives;
        $introspector->visualize($this, $directives);
        var_dump($directives->toString());
    }

    public function testGenerationOfyUMLCodeOnAPHPUnitMatcher()
    {
        $introspector = new UmlReflector\Introspector;
        $directives = new UmlReflector\Directives;
        $introspector->visualize($this->equalTo(new stdClass), $directives);
        var_dump($directives->toString());
    }

    public function testGenerationOfyUMLCodeOnDoctrine2EntityManager()
    {
        $introspector = new UmlReflector\Introspector;
        $directives = new UmlReflector\Directives;
        $em = Test\BaseTestCase::getEm();
        $introspector->visualize($em, $directives);
        var_dump($directives->toString());
    }
}

Here are the pictures obtained by putting the generated directives into yUML (I'll produce also a direct link in the future, but there is a limit of length):

 

 

 

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)