'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; } }