Visualize - Generate a graphic of your models/tables
Based upon the script published by cakeexplorer (http://cakeexplorer.wordpress.com/2007/12/14/build-image-of-current-cake-schema), this script allows you to use Graphviz (http://www.graphviz.org) to generate a graphical representation of your models and/or tables
To install
ensure you are able to run the cake console commands
install graphviz
copy the below script into /app/vendors/shells/visualize.php
To run the script
at the command line:
cd /app/folder/is/here
cake visualize help
cake visualize
The output you'll find in the /app/config/sql folder
You can see a screencast if it in action here: http://www.ad7six.com/files/visualize.mpeg
To install
ensure you are able to run the cake console commands
install graphviz
copy the below script into /app/vendors/shells/visualize.php
To run the script
at the command line:
cd /app/folder/is/here
cake visualize help
cake visualize
The output you'll find in the /app/config/sql folder
You can see a screencast if it in action here: http://www.ad7six.com/files/visualize.mpeg
Download code
<?php
/**
* Visualize console task
*
* This task can be used to generate a graphical representation of your tables or models.
*
* PHP versions 4 and 5
*
* Copyright (c) Tomenko Yevgeny
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @version 1.2.0
* @modifiedby Andy Dawson
* @lastmodified 2007-12-20 23:39:02 +0100 (Thu, 16 Aug 2007) $
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
uses('Folder','File','model'.DS.'connection_manager');
class VisualizeShell extends Shell {
var $DOC_DIR ;
var $PREFIX = APP_DIR;
var $graphToolPath = 'dot.exe'; // cake visualize -tool C:\dev\_tools_\graphviz-2.16\bin\dot.exe
function help() {
$this->out('CakePHP visualise, Usage examples:');
$this->out('cake visualize help');
$this->out(' - this text');
$this->out('cake visualize tables');
$this->out(' - generate graphic based on table structure');
$this->out('cake visualize models');
$this->out(' - generate graphic based on model association definitions');
$this->out('cake visualise [-tool graphVizTool]');
$this->hr();
}
function initialize() {
if (DS == '/') {
$this->graphToolPath = array(
'dot',
'dot -Gmode=heir',
'neato',
'neato -Gmodel=subset'
);
}
$this->DOC_DIR = APP . 'config' . DS . 'sql';
$this->PREFIX= 'img_';
if (isset($this->params['tool'])) {
$this->graphToolPath = $this->params['tool'];
}
return true;
}
function main() {
if (!isset($this->args[0])) {
$this->generateDataFromTables();
$this->writeDotFile($this->DOC_DIR, 't');
$this->generateDataFromModels();
$this->writeDotFile($this->DOC_DIR, 'm');
return;
} elseif ($this->args[0] == 'help') {
$this->help();
return;
} elseif ($this->args[0] == 'tables') {
$mode = 't';
$this->generateDataFromTables();
} elseif ($this->args[0] == 'models') {
$mode = 'm';
$this->generateDataFromModels();
}
$this->writeDotFile($this->DOC_DIR, $mode);
}
function generateDataFromModels() {
foreach($this->getAllModels() as $model) {
$this->out("Looking at model: {$model}");
$model = new $model();
if (!$model->useTable) {
continue;
}
$this->data['tables'][$model->name] = $model->schema(true);
foreach ($this->data['tables'][$model->name] as $attrname => $attr) {
if (!empty($attr['length'])) {
$attr['type'] .= "[{$attr['length']}]";
}
$this->data['nodes'][$model->name][$attrname] = $attr['type'];
if (!empty($attr['default'])) {
$this->data['nodes'][$model->name][$attrname] .= ", default: \\\"{$attr['default']}\\\"";
}
}
foreach($model->__associations as $type) {
foreach ($model->$type as $alias => $association) {
$otherModel = $association['className'];
if ($type == 'belongsTo') {
$this->data['associations'][$model->name.$otherModel] =
array('label'=> $model->name . '->' . $alias, 'node1'=> $model->name, 'node2'=> $otherModel);
} elseif (in_array($type, array('hasOne', 'hasMany'))) {
$this->data['associations'][$otherModel.$model->name] =
array('label'=> $otherModel . '->' . $model->name, 'node1'=> $otherModel, 'node2'=> $model->name);
} elseif ($type == 'hasAndBelongsToMany') {
$names[] = $model->name;
$names[] = $otherModel;
sort($names);
$modelName = implode($names, '');
if (!isset($modelName)) {
$DynamicModel = new Model(array('name'=> $modelName, 'table'=> $association['joinTable']));
$this->data['tables'][$modelName] = $DynamicModel->schema(true);
foreach ($this->data['tables'][$modelName] as $attrname => $attr) {
if (!empty($attr['length'])) {
$attr['type'] .= "[{$attr['length']}]";
}
$this->data['nodes'][$modelName][$attrname] = $attr['type'];
$attrtype = $attr['type'];
if (!empty($attr['default'])) {
$this->data['nodes'][$modelName][$attrname] .= ", default: \\\"{$attr['default']}\\\"";
}
}
$this->data['associations'][$model->name.$otherModel] =
array('label'=> $model->name . '->' . $modelName, 'node1'=> $model->name, 'node2'=> $modelName);
$this->data['associations'][$otherModel.$model->name] =
array('label'=> $otherModel . '->' . $modelName, 'node1'=> $otherModel, 'node2'=> $modelName);
}
}
}
}
}
}
function generateDataFromTables() {
foreach($this->getAllTables() as $table_name) {
$this->out("Looking at table: {$table_name}");
$modelName=$this->_modelName($table_name);
$this->data['tables'][$modelName] = $this->getSchemaInfo($modelName,$table_name);
}
foreach ($this->data['tables'] as $table => $attributes) {
if (is_array($attributes) && count($attributes)>0) {
foreach ($attributes as $attrname => $attr) {
if (substr($attrname, -3) == '_id') {
# Create an association to other table
$otherTable = Inflector::camelize(r('_id','',$attrname));
if (!empty($this->data['tables'][$otherTable])) {
$other_table = $this->data['tables'][$otherTable];
$this->data['associations'][] = array('label'=> $attrname, 'node1'=> $table, 'node2'=> $otherTable);
}
}
if (!empty($attr['length'])) {
$attr['type'] .= "[{$attr['length']}]";
}
$this->data['nodes'][$table][$attrname] = $attr['type'];
$attrtype = $attr['type'];
if (!empty($attr['default'])) {
$this->data['nodes'][$table][$attrname] .= ", default: \\\"{$attr['default']}\\\"";
}
}
}
}
}
function getAllModels() {
$Inflector =& Inflector::getInstance();
uses('Folder');
$folder = new Folder(MODELS);
$models = $folder->findRecursive('.*php');
$folder = new Folder(BEHAVIORS);
$behaviors = $folder->findRecursive('.*php');
$models = array_diff($models, $behaviors);
foreach ($models as $id => $model) {
$file = new File($model);
$models[$id] = $file->name();
}
$models = array_map(array(&$Inflector, 'camelize'), $models);
App::import('Model', $models);
return $models;
}
function getAllTables($useDbConfig = 'default') {
$db =& ConnectionManager::getDataSource($useDbConfig);
$usePrefix = empty($db->config['prefix']) ? '': $db->config['prefix'];
if ($usePrefix) {
$tables = array();
foreach ($db->listSources() as $table) {
if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
$tables[] = substr($table, strlen($usePrefix));
}
}
} else {
$tables = $db->listSources();
}
$this->__tables = $tables;
return $tables;
}
function getSchemaInfo($modelName,$table_name) {
$attrs = array();
if (App::import('model',$modelName)) {
$model = & new $modelName();
$attrs=$model->schema();
return $attrs;
} else {
$DynamicModel = new Model(array('name'=> $modelName, 'table'=> $table_name));
$attrs=$DynamicModel->schema();
return $attrs;
}
return false;
}
function writeDotFile($target_dir, $mode) {
if (!file_exists($target_dir) || !is_dir($target_dir)) {
$this->out("Creating directory \"{$target_dir}\"…");
$folder = & new Folder($target_dir, true);
}
$header = $this->PREFIX+strftime('%Y-%m-%d %H:%M:%S',time());
$version=0;
if ($version > 0) {
$header .= "\\nSchema version $version";
}
$dotFile = $target_dir .DS. 'mode_' . $mode . '.dot';
if (file_exists($dotFile)) {
$f = & new File($dotFile);
$f->delete();
}
$f = & new File($dotFile, true );
// Define a graph and some global settings
$f->append("digraph G {\n");
$f->append("\toverlap=false;\n");
$f->append("\tsplines=true;\n");
$f->append("\tnode [fontname=\"Helvetica\",fontsize=9];\n");
$f->append("\tedge [fontname=\"Helvetica\",fontsize=8];\n");
$f->append("\tranksep=0.1;\n");
$f->append("\tnodesep=0.1;\n");
// $f->append("\tedge [decorate=\"true\"];\n");
// Write header info
$f->append("\t_schema_info [shape=\"plaintext\", label=\"{$header}\", fontname=\"Helvetica\",fontsize=8];\n");
$assocs = array();
// Draw the tables as boxes
foreach ($this->data['nodes'] as $table=>$attributes) {
$f->append("\t\"{$table}\" [label=\"{{$table}|");
foreach ($attributes as $field=>$label) {
$f->append("{$field} : {$label}\\n");
}
$f->append("}\" shape=\"record\"];\n");
}
// Draw the relations
foreach ($this->data['associations'] as $assoc) {
$f->append("\t\"{$assoc['node1']}\" -> \"{$assoc['node2']}\" [label=\"{$assoc['label']}\"]\n");
}
// Close the graph
$f->append("}\n");
$f->close(); // Create the images by using dot and neato (grapviz tools)
$this->out("Generated {$dotFile}\n");
$this->createImgs($dotFile, $target_dir, $mode);
// Remove the .dot file // Keep it for debugging and general info
//$f->delete();
}
function createImgs($dotFile, $path, $mode) {
if (is_string($this->graphToolPath)) {
$commands = array($this->graphToolPath);
} else {
$commands = $this->graphToolPath;
}
uses ('Sanitize');
foreach ($commands as $command) {
$imgFile = $path . DS . 'schematic_' . $mode . '_' . Sanitize::paranoid($command) . ".png";
if (file_exists($imgFile)) {
$f = & new File($imgFile);
$f->delete();
}
if ($this->createImg($command, $dotFile, $imgFile)) {
$this->out("Generated {$imgFile}\n");
} else {
break;
}
}
}
function createImg($command, $dotFile, $imgFile) {
$command = "{$command} -Tpng -o\"{$imgFile}\" \"{$dotFile}\"";
ob_start();
system($command,$return);
ob_clean();
if ($return != 0) {
$this->out("Command Error ($return):\n");
$this->out("$command\n");
return false;
}
return true;
}
}
?>
Comments
Comment
1 How about an example
Comment
2 See the screencast
Comment
3 SLick
Bug
4 App import error
Fatal error: Class 'App' not found in /opt/local/apache2/htdocs/tahfa2/devapp/vendors/shells/visualize.php on line 201
Using 1.2 6311 on OS X - no problems running the console or visualize help.
Any ideas?
Thanks -
Comment
5 for me
/Applications/Graphviz.app/Contents/MacOS/dot
around line 46 I changed the array to
$this->graphToolPath = array(
'/Applications/Graphviz.app/Contents/MacOS/dot',
'/Applications/Graphviz.app/Contents/MacOS/dot -Gmode=heir',
'/Applications/Graphviz.app/Contents/MacOS/neato',
'/Applications/Graphviz.app/Contents/MacOS/neato -Gmodel=subset'
);
Also I was using it with an app actually in the /app folder, the neato stuff I didn't bother to get working yet
Comment
6 Many thanks
Comment
7 fatal error
* I do use a Node model. I've searched for Node1 string (case insensitive) and it was found only in visualize.phpWelcome to CakePHP v1.2.0.6311 beta Console
---------------------------------------------------------------
App : app
Path: /var/www/udc/app
---------------------------------------------------------------
Looking at table: _config_parameters
Looking at table: _config_values
Looking at table: baskets
Looking at table: button_colors
Looking at table: button_patterns
Looking at table: button_styles
Looking at table: buttons
Looking at table: comments
Looking at table: descriptions
Looking at table: forums
Looking at table: images
Looking at table: in_bask
Looking at table: in_baskets
Looking at table: metadata
Looking at table: nodes
Looking at table: operations_logs
Looking at table: relations
Looking at table: sites
Looking at table: sites_descriptions
Looking at table: tags
Looking at table: threads
Looking at table: users
Generated /var/www/udc/app/config/sql/mode_t.dot
Generated /var/www/udc/app/config/sql/schematic_t_dot.png
Generated /var/www/udc/app/config/sql/schematic_t_dotGmodeheir.png
Generated /var/www/udc/app/config/sql/schematic_t_neato.png
Generated /var/www/udc/app/config/sql/schematic_t_neatoGmodelsubset.png
Looking at model: User
Looking at model: Forum
Looking at model: Basket
Looking at model: Image
Looking at model: Thread
Looking at model: Node1
Fatal error: Class 'Node1' not found in /var/www/udc/app/vendors/shells/visualize.php on line 82
* apache 2.2.4 / PHP Version 5.2.3-1ubuntu6.3 on Ubuntu 7.10
Any idea what might be going on under the hood?
Comment
8 Re Fatal Error
Comment
9 Newer version..
i just tried the visualizer out and i liked it really much! One thing that disturbed me though was
- the lack of information about the binding (eg hasOne,belongsTo,etc)
- the duplicate entries, resulting from table and model analysis
i tried to correct both errors, and it seems to work rather well. you can find the code here: http://pastebin.com/f78f99e58
please note that it's almost totally untested, and there may be some errors. : )