php__phplib/OSInet/Class_Grapher/Graph.php

263 lines
7.7 KiB
PHP

<?php
namespace OSInet\Class_Grapher;
/**
* Graph builder.
*/
class Graph {
/**
* A PEAR class.
*
* @var Image_GraphViz
*/
public $g;
/**
* @var Logger
*/
public $logger = NULL;
var $runtimeClasses = array();
var $runtimeInterfaces = array();
var $classes = array();
var $interfaces = array();
var $registeredClasses = array();
var $registeredInterfaces = array();
/**
* Represent attributes as a string in GraphViz format.
*
* @param array $attributes
*/
public function attributes(array $attributes) {
$attributes = $this->g->_escapeArray($attributes);
$ret = array();
foreach ($attributes as $name => $value) {
$ret[] = "$name=$value";
}
$ret = implode(',', $ret);
return $ret;
}
/**
* Shortcut to invoke logger->message().
*
* @param string $message
* @param int $level
*/
public function debug($message = '\n', $level = LOG_DEBUG) {
$this->logger->debug($message, $level);
}
/**
* Build a list of files to scan below a given base.
*
* TODO support base as a file instead of a directory.
*
* @param string $base
* A starting path or array of paths for file lookup.
* @param array $blackList
* An array of file names to reject, whatever their path.
* @param string $pattern
* A regexp for file paths to match.
*
* @return array
* An array of compliant file paths.
*/
public function getFiles($base, $blackList = array(),
$pattern = '/.*\.(inc|module|php)$/') {
$files = array();
foreach ($base as $start) {
$dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($start,
\FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
while ($dir->valid()) {
$name = $dir->key();
if (preg_match($pattern, $name) && !in_array(basename($name), $blackList)) {
$files[] = $name;
}
elseif ($this->logger->debugLevel >= LOG_INFO) {
if (in_array(basename($name), $blackList)) {
$this->debug("$name blacklisted.\n", LOG_INFO);
}
else {
$this->debug("$name does not match source paths.\n", LOG_DEBUG);
}
}
$dir->next();
}
}
$files = array_unique($files);
sort($files);
return $files;
}
/**
* Constructor helper: initialize GraphViz settings.
*/
public function initGraph() {
$this->g = new \Image_Graphviz();
$this->g->setDirected(TRUE);
$this->g->setAttributes(array(
'rankdir' => 'LR',
'overlap' => FALSE,
));
}
public function registerClass(ClassInstance $class) {
$this->debug(' Registering class '. $class->name . "\n");
if (!empty($class->parent)) {
if (!isset($this->registeredClasses[$class->parent])) {
$this->debug(" Registering parent ". $class->parent . " for " . $class->name . "\n");
$gpParent = new \PGPClass($class->parent);
$cpParent = new ClassInstance($gpParent, 1, $this->logger);
$this->registerClass($cpParent);
}
}
foreach ($class->interfaces as $interface) {
if (!isset($this->registeredClasses[$interface])) {
$this->debug(" Registering interface $interface for {$class->name}\n");
$gpInterface = new \PGPClass($interface);
$cpInterface = new InterfaceInstance($gpInterface, 1, $this->logger);
$this->registerInterface($cpInterface);
}
}
// At this point, all ascendants are registered.
if (!isset($this->registeredClasses[$class->name])) {
$this->registeredClasses[$class->name] = $class;
}
// Override implicit status of a previously declared class if needed.
if (!$class->implicit) {
$this->registeredClasses[$class->name]->implicit = 0;
}
}
public function registerInterface(InterfaceInstance $interface) {
$this->debug("Registering interface {$interface->name}\n");
if (!empty($interface->parent)) {
if (!isset($this->registeredInterfaces[$interface->parent])) {
$this->debug(" Registering parent ". $interface->parent . " for " . $interface->name . "\n");
$gpParent = new \PGPClass($interface->parent);
$cpParent = new InterfaceInstance($gpParent, 1, $this->logger);
$this->registerInterface($cpParent);
}
}
// At this point, all ascendants are registered.
if (!isset($this->registeredInterfaces[$interface->name])) {
$this->registeredInterfaces[$interface->name] = $interface;
}
// Override implicit status of a previously declared interface if needed.
if (!$interface->implicit) {
$this->registeredInterfaces[$interface->name]->implicit = 0;
}
}
/**
* Symbol extractor using grammar_parser.
*
* @param string $base
*/
public function extractGrammar($base) {
// These files cause a segfault during parse, so we skip them for now.
// Possible patch at http://drupal.org/node/1485370
$blackList = array(
'RouteCollection.php',
'HeaderBag.php',
'NativeSessionHandler.php',
'SimpleXMLElement.php',
'ContainerBuilderTest.php',
'YamlFileLoaderTest.php',
'ResponseTest.php',
'ConfigDataCollectorTest.php',
'KernelTest.php',
'UrlMatcherTest.php',
);
$files = $this->getFiles($base, $blackList);
foreach ($files as $filename) {
$source = file_get_contents($filename, FALSE);
$reader = new \PGPReader($source);
unset($source);
$this->debug("Parsing $filename\n", LOG_INFO);
$reader->buildGrammar();
foreach ($reader->getInterfaces() as $interface) {
$this->registerInterface(new InterfaceInstance($interface->data, 0, $this->logger));
}
foreach ($reader->getClasses() as $class) {
$this->registerClass(new ClassInstance($class->data, 0, $this->logger));
}
$reader->reset();
unset($reader);
}
}
public function __construct($base, Logger $logger) {
$this->logger = $logger;
$this->extractGrammar($base);
$this->initGraph();
}
public function implementsAttributes() {
$attributes = array(
'arrowhead' => 'empty',
'arrowtail' => 'none',
'style' => 'dotted',
);
$attributes = $this->g->_escapeArray($attributes);
return $attributes;
}
public function buildSymbols($collection = array(), $kind = NULL) {
foreach ($collection as $name => $symbol) {
$this->debug("Building $kind $name\n");
$this->g->addNode($name, $symbol->attributes());
if ($symbol->parent) {
$this->g->addEdge(array($name => $symbol->parent));
}
if (empty($symbol->interfaces)) {
continue;
}
foreach ($symbol->interfaces as $interface_name) {
$this->g->addEdge(array($name => $interface_name), $this->implementsAttributes());
}
}
}
/**
* Build the graph once extraction has been performed.
*/
public function build($imager = 'dot', $format = 'svg') {
$this->debug("Starting build for "
. count($this->registeredInterfaces) . " interfaces and "
. count($this->registeredClasses) . " classes.\n",
WATCHDOG_INFO);
$this->buildSymbols($this->registeredInterfaces, 'interface');
$this->buildSymbols($this->registeredClasses, 'class');
if (empty($imager) || $imager == 'dump') {
// $this->debug(print_r($g->_getGroups(), TRUE));
$this->debug("Interfaces:\n");
$this->debug(print_r($this->registeredInterfaces, TRUE));
$this->debug("Classes\n");
$this->debug(print_r($this->registeredClasses, TRUE));
//$this->debug(print_r($this->g));
$ret = $this->g->parse();
}
else {
ob_start();
$this->g->image($format, $imager);
$ret = ob_get_contents();
ob_end_clean();
}
return $ret;
}
}