'UML Diagrams',
'page callback' => 'drupal_get_form',
'page arguments' => array('graphapi_class_form'),
'route_name' => 'graphapi_class.select',
'access arguments' => array('access devel information'),
);
return $items;
}
function graphapi_class_form($form, &$form_state) {
$request = drupal_container()->get('request');
$types = $request->query->get('ids');
if (empty($types)) {
$types = array('stdClass');
}
$types = array_combine($types, $types);
ksort($types);
graphapi_loader();
$classes = get_declared_classes();
asort($classes);
$classes = array_combine($classes, $classes);
$interfaces = get_declared_interfaces();
asort($interfaces);
$interfaces = array_combine($interfaces, $interfaces);
$kinds = array(
'services' => _graphapi_class_services(),
'classes' => $classes,
'interfaces' => $interfaces,
);
$form['class'] = array(
'#type' => 'select',
'#title' => 'Select your class or interface',
'#description' => 'This class will be displayed as part of a Class Diagram.',
'#options' => $kinds,
'#required' => TRUE,
'#multiple' => TRUE,
'#size' => 20,
'#default_value' => $types,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
$form['result'] = array(
'#title' => 'Classes',
'#prefix' => '
',
'#markup' => graphapi_class_build($types),
'#suffix' => '
',
);
return $form;
}
function graphapi_class_form_submit(&$form, &$form_state) {
$class = $form_state['values']['class'];
$list = join('+', $class);
$options = array(
'absolute' => TRUE,
'query' => array(
'ids' => array_keys($class),
),
);
$url = url('admin/config/system/graphapi/uml', $options);
$form_state['redirect'] = array(
$url,
$options,
);
drupal_redirect_form($form_state);
}
/**
* Generate a hopefully consistent cache id
*
* We assume $item when recursive is properly sorted
*
* @param array $item
* @return string
*/
function _graphapi_class_get_cid(array $item) {
ksort($item);
$cid = serialize($item);
$cid = md5($cid);
return $cid;
}
function _graphapi_class_get_cache($cid) {
return cache()->get($cid);
}
function _graphapi_class_cache_set($cid, $data) {
return cache()->set($cid, $data);
}
function _graphapi_class_services() {
$yaml_discovery = new \Drupal\Component\Discovery\YamlDiscovery('services', \Drupal::moduleHandler()->getModuleDirectories());
$yaml = $yaml_discovery->findAll();
$classes = array();
foreach ($yaml as $module => $services) {
//dsm($services, $module);
foreach ($services['services'] as $key => $value) {
$classes[$value['class']] = "$module:$key";
}
}
//dsm($classes, __FUNCTION__);
return array_keys($classes);
}
/**
* Generates a UML CLass Diagram for the given format.
*
* @param array $classes
* List of class and interface names
* @param array $config
* Settings for both builder as the rendering
* generate-script : the dot textual output
* generate-image: generate and svg or other image format
* only-self : see ClassDiagramBuilder for more info
* only-public : see ClassDiagramBuilder
* show-constants : see ClassDiagramBuilder
* add-parents : see ClassDiagramBuilder
*
* @return string
* The output is rendered as a group of classes seperated by a HR
* TODO: is a HR the best seperation.
*
* @see ClassDiagramBuilder
*/
function graphapi_class_build_class(array $classes = array(), array $config = array()) {
graphapi_loader();
$config += array(
'generate-script' => FALSE,
'generate-image' => TRUE,
// 'type' => 'svg',
);
$options = array(
'script' => $config['generate-script'],
'render' => $config['generate-image'],
);
unset($config['generate-script']);
unset($config['generate-image']);
// $cid = _graphapi_class_get_cid($classes, $script_only);
// if ($o = _graphapi_class_get_cache($cid)) {
// return $o->data;
// }
$result = array();
$g = new Graph();
$loader = new ClassDiagramBuilder($g);
foreach ($config as $option => $value) {
$loader->setOption($option, $value);
}
// Reduce output.
//$loader->setOption('only-self', TRUE);
//$loader->setOption('only-public', TRUE);
//$loader->setOption('show-constants', FALSE);
$messages[] = array();
try {
$i=0;
foreach ($classes as $class) {
if ($i++ % 100 == 0) {
watchdog("graphapi_class", "Progressing to $i items.");
}
// Do not try to add an already added class (through reflection)
if (!$g->hasVertex($class)) {
try {
$loader->createVertexClass($class);
}
catch (Exception $exc) {
$message = $exc->getMessage();
$messages[$class] = $message;
}
}
}
}
catch (Exception $exc) {
return $exc->getMessage();
}
$render_options = array(
'graphapi' => $options,
);
// $options = array(
// 'graphapi' => array(
// 'script' => FALSE,
// 'render' => TRUE,
// )
// );
foreach ($loader->createGraphsComponents() as $graph) {
$result[] = graphapi_render($graph, $render_options);
}
$total = join("
", $result);
//_graphapi_class_cache_set($cid, $total);
return $total;
}
function graphapi_class_cron() {
$last_run = _graphapi_class_get_cache(__FUNCTION__);
// Only generate every hour
if ($last_run && $last_run->data + 3600 > time()) {
return;
}
_graphapi_class_cache_set(__FUNCTION__, time());
_graphapi_class_generate_run();
}
function _graphapi_class_generate_run() {
graphapi_loader();
$queue = Drupal::queue('graphapi_class_generator');
$classes = graphapi_class_get_all();
$count = count($classes);
$scheduled = 0;
$options = array(
'add-parents' => 1,
);
foreach ($classes as $path => $class) {
$item = array(
'classes' => array($class),
'path' => $path,
'options' => $options,
);
$cid = _graphapi_class_get_cid($item);
$cached_item = _graphapi_class_get_cache($cid);
if ($cached_item === FALSE) {
$scheduled++;
$queue->createItem($item);
}
}
watchdog('graphapi_class', 'Scheduled %scheduled out of %count classes and interfaces.', array('%scheduled' => $scheduled, '%count' => $count));
}
/**
* Implements hook_queue_info().
*/
function graphapi_class_queue_info() {
$queues['graphapi_class_generator'] = array(
'title' => t('Generate all UML Graphs'),
'worker callback' => 'graphapi_class_generator_worker',
'cron' => array(
'time' => 60,
),
);
return $queues;
}
/**
* Queue worker callback.
*/
function graphapi_class_generator_worker($item) {
$path_prefix = "graphapi/uml/classdiagrams/";
if (isset($item['classes'])) {
$classes = $item['classes'];
}
else {
$classes = array($item['class']);
}
$path = 'public://' . $path_prefix . $item['path'] . '.html';
$options = $item['options'];
$result = graphapi_class_build_class($classes, $options);
$cid = _graphapi_class_get_cid($classes);
_graphapi_class_cache_set($cid, $result);
@drupal_mkdir(dirname($path), null, TRUE);
file_put_contents($path, $result);
}
function graphapi_class_get_all() {
static $classes;
if (!isset($classes)) {
$classes = &drupal_static(__FUNCTION__, array());
}
else {
return $classes;
}
graphapi_loader();
$loader = drupal_classloader();
// Directories to look for
$prefixes = $loader->getPrefixes();
$root = $_SERVER["DOCUMENT_ROOT"];
$skip_prefixes = array(
'Zend',
'Symfony',
'Twig', // Generates error: Class Twig/Autoloader.php does not exist
);
foreach ($prefixes as $prefix => $dirs) {
$skip = FALSE;
foreach ($skip_prefixes as $test_prefix) {
if (strpos($prefix, $test_prefix) === 0) {
$skip = TRUE;
}
}
if ($skip) {
watchdog('graphapi_class', "Skipping from exclude listfor '%prefix'", array('%prefix' => $prefix), WATCHDOG_NOTICE);
continue;
}
foreach ($dirs as $dir) {
$class_finder = new Finder();
try {
// @see http://symfony.com/doc/current/components/finder.html
// We scan each directory individually due to
// - https://drupal.org/node/2176683 : We register invalid components to composer.json and in other ways
$class_finder->files()->name('*.php')->in($dir);
}
catch (Exception $exc) {
watchdog('graphapi_class', "Exception while scanning for '%prefix' directory '%dir'", array('%prefix' => $prefix, '%dir' => $dir), WATCHDOG_WARNING);
continue;
}
foreach ($class_finder as $file) {
// Path relative to it's scan dir
$real_path = $file->getRealPath();
$path = str_replace($root, '', $real_path);
$class = $file->getRelativePathname();
// scrub .php and toggle /
$class = preg_replace("/.php$/", '', $class);
$class = str_replace('/', '\\', $class);
$classes[$path] = $class;
}
}
}
watchdog('graphapi_class', "Found %count classes and interfaces", array('%count' => count($classes)), WATCHDOG_INFO);
return $classes;
}
/**
* Implements hook_help().
*/
function graphapi_class_help($path, $arg) {
if ($path == 'admin/help#graphapi_class') {
$output = '';
$output .= '' . t('About') . '
';
$output .= '' . t('The Graph API sub module graphapi_class generates UML Diagrams') . '
';
$output .= '' . t('Uses') . '
';
$output .= '';
$output .= '- ' . t('Using input filter Class Diagram') . '
';
$output .= '- ' . 'FilterTrivialGraphFormat::help(TRUE)' . '
';
$output .= '
';
return $output;
}
}