Takes a passed in php file (and optional inc path extensions) and returns UML diagrams for all of the classes that it finds, travelling recursivly through the source code.

Very hacky but helps me get through my job of fixing up old undocumented php scripts.

Update: BTW, the more I think about this code, the more I don't like it. Any critiques would be most welcome.

#!/usr/bin/perl -w use strict; use warnings; use Carp; use File::Basename; use Data::Dumper; use Cwd; use constant DEBUG => 0; # Core Vars my %uml_data; # Hash to hold the UML data # INC ARRAY # >> SET THIS TO YOUR PHP INC ARRAY. << my $inc = './;/www/lib/pear-lib'; $::VERSION = '1.0.3a'; # Regular Expresions my $require_once_RE = '^\s*require_once.*"(.+)"'; my $class_RE = '^\s*class\s+'; my $class_name_RE = $class_RE . '(\w+)'; my $extends_RE = 'extends\s+(\w+)'; my $parent_class_RE = $class_name_RE . '\s+' . $extends_RE; my $var_RE = '^\s*var\s+(\$\w+)\s*'; my $var_defined_RE = $var_RE . '(.*);'; my $function_RE = '^\s*function\s+'; my $function_name_RE = $function_RE . '(&?\w+)\s*'; my $function_params_RE = $function_name_RE . '\((.*)\)'; # MAIN my $the_file = $ARGV[0] or die("USAGE: perl phpuml.pl filename [inc_pa +th;more_inc_path]\n"); if ($ARGV[1]) { $inc .= ";" . $ARGV[1]; } process($the_file); print report(); print Dumper(%uml_data) if DEBUG; sub process { my $file = shift; my @tried; # variable to hold typeglob for filehandle to be processed my $fh; # Are we in testing mode? if ($file eq "TEST") { # If so, use the test file at the end of this file. $fh = *main::DATA; } elsif ($file) { unless(open(PHP_FILE, "< $file")) { my @inc_array = split /;/, $inc; INC: foreach my $folder (@inc_array) { my $f = $folder . $file; open(PHP_FILE, "< $f") || next INC; last INC; } } $fh = *PHP_FILE; } else { die("Must pass file for processing"); } ################################### ## LETS GET READY TO RUMBLE!!!!! ## ################################### # Declare temp vars to hold values passed to the data hash my $class_name = ""; my $parent = ""; my @functions; my @fields; my $holding; # Flag used when function parameters extend over a single line. my $in_multi_line_parse = 0; # Flag used when there is more than one class in a file. my $i_have_seen_a_class = 0; FILE_LOOP: while(<$fh>) { if (/$require_once_RE/i) { my $fn = $1; push (@tried, $fn); } elsif (/$class_name_RE/i) { if ($i_have_seen_a_class) { unless ($uml_data{$class_name}) { $uml_data{$class_name} = { FILENAME => basename($file), INHERITS => $parent, FIELDS => [@fields], METHODS => [@functions] }; } # clear the parameters. @fields = (); @functions = (); } else { $i_have_seen_a_class = 1; } $class_name = $1; if (/$extends_RE/i) { $parent = $1; } } elsif (/$var_RE/i) { my $var = $1; if (/$var_defined_RE/i) { my $val = $2; $val =~ s/\s//g; $var = $var . $val; } push @fields, $var; } elsif (/$function_RE/i) { if ($class_name) { if (/$function_params_RE/i) { # All the parameters on one line my $func = $1 . "(" . $2 . ")"; $func =~ s/\s//g; push @functions, $func; } else { $in_multi_line_parse = 1; chomp; $holding = $_; } } } elsif ($in_multi_line_parse) { # We are in a subsequent line of a function # So we are passing what we know with what we # will know. s/\s//g; # Strip off white space chomp; if (/\)/) { # Reset the multiline flag. $in_multi_line_parse = 0; $holding .= $_; $holding =~ /$function_params_RE/i; my $func = $1 . "(" . $2 . ")"; $func =~ s/\s//g; push @functions, $func; } else { $holding .= $_; } } } # Send class hash to _data; unless ($uml_data{$class_name}) { $uml_data{$class_name} = { FILENAME => basename($file), INHERITS => $parent, FIELDS => [@fields], METHODS => [@functions] }; } foreach my $rec_file (@tried) { process($rec_file); } return; #print "\n*****--->>>>>> " . $self->{_data}->{foo}->{fields}[0] . +"\n"; } # process sub report { my $txt = "/****************************************************** +\n *\n"; while ((my $key, my $value) = each (%uml_data)) { $txt .= class_report($key); } $txt .= " ******************************************************/\ +n "; return $txt; } sub class_report { my $class_name = shift; # Class name my $txt = " *****************************************************\ +n"; $txt .= " * $class_name"; if ($uml_data{$class_name}{INHERITS}) { $txt .= " inherits " . $uml_data{$class_name}{INHERITS}; } $txt .= "\n"; $txt .= " *****************************************************\ +n"; # Class vars foreach my $var (@{$uml_data{$class_name}{FIELDS}}) { my $marker = "+"; if ($var =~ /^\$_/) { $marker = "-"; } $txt .= " * $marker $var\n"; } $txt .= " *****************************************************\ +n"; # Class methods foreach my $method (@{$uml_data{$class_name}{METHODS}}) { my $marker = "+"; if ($method =~ /^_/) { $marker = "-"; } #print Dumper($method); $txt .= " * $marker $method\n"; } $txt .= " ****************************************************\n + *\n"; return $txt; } # class_report if (DEBUG) { # process("D:/Php/pear/HTML/Table.php"); print "####################################################\n"; # print ">>>>>>>> " . $uml_data{Alert}{FIELDS}[1]; } # Module Test Data # # This is an OO php file with three classes, one extending the other # # It includes some challenges to the generator such as method calls # across lines. __DATA__ <?php /** * Foobar test class * Copyright (C) 2002 Christopher Raymond Baker * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-13 +07, USA. * * Christopher Raymond Baker [cbaker@cbaker.org] * http://cbaker.org */ /* ******************** *\ * REQUIRED LIBRARIES \* ******************** */ // PEAR Libraries require_once "PEAR.php"; require_once "HTML/Table.php"; define("DEFAULT_CSS", "3px solid red"); class Foo { var $flub; function Foo() { die("There is no there there."); } function rose($rose = "rose") { print "A "; while (1) { print "$rose is a "; } } // end func rose function _private($foo, $bar = "") { print "Hi Mom!"; } // end func _private } Class Box extends PEAR { /** * The width for the box */ var $_width; /** * The height for the box */ var $_height; /** * Field for holding the state of the class. */ var $state; function Box($w, $h = 0) { $this->state = true; if (is_int($w)) { $this->_width = $w; } else { $this->state = new PEAR_Error("Not a valid width dimension +.", NOT_AN_INTEGER_ERROR); } if (is_int($h)) { $this->_width = $h; } else { $this->state = new PEAR_Error("Not a valid height dimensio +n.", NOT_AN_INTEGER_ERROR); } } // end constructor function &factory($w, $h = 0) { $b = new Box($w, $h); if (Test::isGood($b->state)) return $b; return $b->state; } // end func factory } // end class Box class Alert extends Box { /** * Box object dimensions for the component. */ var $_dim; var $_message; var $_caption; /** * Field for holding the state of the class. */ var $state; function Alert($m, $class = "", $i = "") { $this->_dim =& Box::factory(400); if(is_string($m)) { $this->_message = $m; } elseif (is_array($m)) { } } // end constructor function setDimensions($r, $h = 0) { if (get_class($r) == "box") { $_dim = $r; } } // end func setDimensions } // end class Alert

Replies are listed 'Best First'.
Re: PHP UML Diagram Generator
by rjray (Chaplain) on Mar 01, 2002 at 00:16 UTC

    Nice. Have you looked at the XML-based ways of expressing UML, such as those used by ArgoUML? Adopting something like that would allow you to bring up the UML snippets in more traditional CASE-oriented tools.

    --rjray

      Great idea! I've played with Argo but didn't know that it used XML. (Duhh!)

      UPDATE: Having looked into it, the standard that ArgoUML is using the OMGs XMI Specification for XML Metadata Interchange. (Corrected info on XMI)

      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      ()-()                                                      ()-()
       \"/    DON'T   BLAME   ME,   I  VOTED   FOR    PACO!       \"/
        `                                                          ` 
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      

        Out of curiousity, I created a simple two-class UML diagram last night and pulled the XMI file out of the zipped-argo save-file that ArgoUML creates.

        I wouldn't wish that format on anyone, not even as machine-generated output.

        --rjray