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

In reply to PHP UML Diagram Generator by ignatz

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.