Version: current

      1 <?php
      2 /**
      3  * Gives you a nice way of viewing your data model.
      4  * Access at dev/viewmodel.
      5  *
      6  * Requirements: http://graphviz.org/
      7  *
      8  * @package sapphire
      9  * @subpackage tools
     10  */
     11 class ModelViewer extends Controller {
     12     static $url_handlers = array(
     13         '$Module!' => 'handleModule',
     14     );
     15
     16     protected $module = null;
     17
     18     function handleModule($request) {
     19         return new ModelViewer_Module($request->param('Module'));
     20     }
     21
     22     function init() {
     23         parent::init();
     24
     25         $canAccess = (Director::isDev() || Director::is_cli() || Permission::check("ADMIN"));
     26         if(!$canAccess) return Security::permissionFailure($this);
     27
     28         // check for graphviz dependencies
     29         $returnCode = 0;
     30         $output = array();
     31         exec("which neato", $output, $returnCode);
     32         if($returnCode != 0) {
     33             user_error(
     34                 'You don\'t seem to have the GraphViz library (http://graphviz.org/) and the "neato" command-line utility available',
     35                 E_USER_ERROR
     36             );
     37         }
     38     }
     39
     40     /**
     41      * Model classes
     42      */
     43     function Models() {
     44         $classes = ClassInfo::subclassesFor('DataObject');
     45         array_shift($classes);
     46         $output = new DataObjectSet();
     47         foreach($classes as $class) {
     48             $output->push(new ModelViewer_Model($class));
     49         }
     50         return $output;
     51     }
     52
     53     /**
     54      * Model classes, grouped by Module
     55      */
     56     function Modules() {
     57         $classes = ClassInfo::subclassesFor('DataObject');
     58         array_shift($classes);
     59
     60         $modules = array();
     61         foreach($classes as $class) {
     62             $model = new ModelViewer_Model($class);
     63             if(!isset($modules[$model->Module])) $modules[$model->Module] = new DataObjectSet();
     64             $modules[$model->Module]->push($model);
     65         }
     66         ksort($modules);
     67         unset($modules['userforms']);
     68
     69         if($this->module) {
     70             $modules = array($this->module => $modules[$this->module]);
     71         }
     72
     73         $output = new DataObjectSet();
     74         foreach($modules as $moduleName => $models) {
     75             $output->push(new ArrayData(array(
     76                 'Link' => 'dev/viewmodel/' . $moduleName,
     77                 'Name' => $moduleName,
     78                 'Models' => $models,
     79             )));
     80         }
     81
     82         return $output;
     83     }
     84 }
     85
     86 /**
     87  * @package sapphire
     88  * @subpackage tools
     89  */
     90 class ModelViewer_Module extends ModelViewer {
     91     static $url_handlers = array(
     92         'graph' => 'graph',
     93     );
     94
     95     /**
     96      * ModelViewer can be optionally constructed to restrict its output to a specific module
     97      */
     98     function __construct($module = null) {
     99         $this->module = $module;
    100
    101         parent::__construct();
    102     }
    103
    104     function graph() {
    105         SSViewer::set_source_file_comments(false);
    106         $dotContent = $this->renderWith("ModelViewer_dotsrc");
    107         $CLI_dotContent = escapeshellarg($dotContent);
    108
    109         $output= `echo $CLI_dotContent | neato -Tpng:gd &> /dev/stdout`;
    110         if(substr($output,1,3) == 'PNG') header("Content-type: image/png");
    111         else header("Content-type: text/plain");
    112         echo $output;
    113     }
    114 }
    115
    116 /**
    117  * Represents a single model in the model viewer
    118  *
    119  * @package sapphire
    120  * @subpackage tools
    121  */
    122 class ModelViewer_Model extends ViewableData {
    123     protected $className;
    124
    125     function __construct($className) {
    126         $this->className = $className;
    127         parent::__construct();
    128     }
    129
    130     function getModule() {
    131         global $_CLASS_MANIFEST;
    132         $className = $this->className;
    133         if(($pos = strpos($className,'_')) !== false) $className = substr($className,0,$pos);
    134         if(isset($_CLASS_MANIFEST[$className])) {
    135             if(preg_match('/^'.str_replace('/','\/',preg_quote(BASE_PATH)).'\/([^\/]+)\//', $_CLASS_MANIFEST[$className], $matches)) {
    136                 return $matches[1];
    137             }
    138         }
    139     }
    140
    141     function getName() {
    142         return $this->className;
    143     }
    144
    145     function getParentModel() {
    146         $parentClass = get_parent_class($this->className);
    147         if($parentClass != "DataObject") return $parentClass;
    148     }
    149
    150     function Fields() {
    151         $output = new DataObjectSet();
    152
    153         $output->push(new ModelViewer_Field($this,'ID', 'PrimaryKey'));
    154         if(!$this->ParentModel) {
    155             $output->push(new ModelViewer_Field($this,'Created', 'Datetime'));
    156             $output->push(new ModelViewer_Field($this,'LastEdited', 'Datetime'));
    157         }
    158
    159         $db = singleton($this->className)->uninherited('db',true);
    160         if($db) foreach($db as $k => $v) {
    161             $output->push(new ModelViewer_Field($this, $k, $v));
    162         }
    163         return $output;
    164     }
    165
    166     function Relations() {
    167         $output = new DataObjectSet();
    168
    169         foreach(array('has_one','has_many','many_many') as $relType) {
    170             $items = singleton($this->className)->uninherited($relType,true);
    171             if($items) foreach($items as $k => $v) {
    172                 $output->push(new ModelViewer_Relation($this, $k, $v, $relType));
    173             }
    174         }
    175         return $output;
    176     }
    177 }
    178
    179 /**
    180  * @package sapphire
    181  * @subpackage tools
    182  */
    183 class ModelViewer_Field extends ViewableData {
    184     public $Model, $Name, $Type;
    185
    186     function __construct($model, $name, $type) {
    187         $this->Model = $model;
    188         $this->Name = $name;
    189         $this->Type = $type;
    190
    191         parent::__construct();
    192     }
    193 }
    194
    195 /**
    196  * @package sapphire
    197  * @subpackage tools
    198  */
    199 class ModelViewer_Relation extends ViewableData {
    200     public $Model, $Name, $RelationType, $RelatedClass;
    201
    202     function __construct($model, $name, $relatedClass, $relationType) {
    203         $this->Model = $model;
    204         $this->Name = $name;
    205         $this->RelatedClass = $relatedClass;
    206         $this->RelationType = $relationType;
    207
    208         parent::__construct();
    209     }
    210
    211 }
    212
    213 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -