Статьи

Генерация PHP UML из графа живых объектов

Иногда вам нужно поделиться дизайном со своими коллегами. Вы можете пройтись по нему по коду и объяснить, какие классы и интерфейсы вы создали, но есть более абстрактные модели, которые вы можете показать ему, чтобы он быстро понял картину.

Одним из таких инструментов является UML и, в частности, диаграммы классов. Хорошая диаграмма классов может показать отношения между дюжиной классов и, таким образом, проектирование всего небольшого компонента.
Однако я не сторонник разработки программного обеспечения до написания кода, поскольку этот код является окончательным документом проекта. Когда мы готовим UML-диаграммы, мы, в основном, находимся в стадии после того, как мы протестировали код и зафиксировали его. Цель — быстро поделиться информацией, а затем выбросить диаграмму, когда она не синхронизируется с кодом.

Как?

Один из подходов к созданию диаграмм такого рода состоит в том, чтобы один из членов дуэта парного программирования рисовал на бумаге карандашом и резиной . Бумага по-прежнему является лучшим выразительным средством, а карандаш позволяет легко редактировать. В некоторых случаях мы даже обновляем наш бумажный лист во время извлечения классов на этапе рефакторинга.

Даже когда вы рисуете Uml с помощью компьютерного инструмента, скучная часть рисует диаграмму . В конце концов, часть информации уже встроена в код; и мы уже знаем, что эта диаграмма будет недолгой, так как код развивается быстро.

В качестве подтверждения концепции я начал писать инструмент PHP UML Generator, который извлекает диаграмму классов Uml из графа объектов PHP , который ранее был создан на некоторой фабрике. Это базовый реверс-инжиниринг.

Существующие инструменты, такие как Doxygen, создают диаграммы такого рода, но начиная со статического анализа кода, а не с графика живых объектов. Это означает, что Umlntrospector будет сложнее интегрировать, так как вам нужно загрузить приложение, но я думаю, что это перевешивается двумя факторами:

  • единственным источником истины является граф объектов : если аннотации устарели или отсутствуют, если у вас есть работающий набор тестов, граф объектов будет синхронизирован с текущим состоянием кода.
  • Решение более простое : Reflection используется для извлечения информации вместо анализа комментариев и кода docblock.

Характеристики

Поскольку это подтверждение концепции, я постарался создать минимально жизнеспособный продукт:

  • по умолчанию интроспектор создает UML-определения для Yuml.me , используемые для визуализации. Другие адаптеры могут быть добавлены.
  • Интроспектор следит за композицией и наследованием до N уровней : классовые отношения важнее методов, которых также, вероятно, будет слишком много, чтобы их показать.
  • В настоящее время интроспектор не различает объекты одного и того же класса (целью является диаграмма классов, но они могут представлять собой больше ассоциаций, названных по-разному).
  • Он может игнорировать пространства имен библиотек, такие как Zend_ или Doctrine_.
  • Он пропускает скалярные поля, поскольку они почти не предоставляют информации.
  • Он учитывает имена базовых классов и пространства имен PHP 5.3.

Вывод

В этой статье я провожу с сообществом идею анализа графа живых объектов. Вот несколько очень маленьких примеров результатов, полученных для объектов PHPUnit и Doctrine 2 Entity Manager с этим кодом (не может работать автономно, он включен только для пояснения):

<?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());
    }
}

Вот изображения, полученные путем помещения сгенерированных директив в yUML (в будущем я также приведу прямую ссылку, но есть ограничение длины):