Add description to Filters.
[project/graphapi.git] / modules / graphapi_class / graphapi_class.module
1 <?php
2
3 use Fhaculty\Graph\Uml\ClassDiagramBuilder;
4 use Fhaculty\Graph\GraphViz;
5 use Fhaculty\Graph\Graph;
6 use Symfony\Component\Finder\Finder;
7 use Drupal\graphapi_class\Plugin\Filter\FilterTrivialGraphFormat;
8
9 /**
10 * Implements hook_menu().
11 */
12 function graphapi_class_menu() {
13 $items['admin/config/system/graphapi/uml'] = array(
14 'title' => 'UML Diagrams',
15 'page callback' => 'drupal_get_form',
16 'page arguments' => array('graphapi_class_form'),
17 'route_name' => 'graphapi_class.select',
18 'access arguments' => array('access devel information'),
19 );
20 return $items;
21 }
22
23 function graphapi_class_form($form, &$form_state) {
24 $request = drupal_container()->get('request');
25 $types = $request->query->get('ids');
26 if (empty($types)) {
27 $types = array('stdClass');
28 }
29 $types = array_combine($types, $types);
30 ksort($types);
31 graphapi_loader();
32 $classes = get_declared_classes();
33 asort($classes);
34 $classes = array_combine($classes, $classes);
35 $interfaces = get_declared_interfaces();
36 asort($interfaces);
37 $interfaces = array_combine($interfaces, $interfaces);
38 $kinds = array(
39 'services' => _graphapi_class_services(),
40 'classes' => $classes,
41 'interfaces' => $interfaces,
42 );
43 $form['class'] = array(
44 '#type' => 'select',
45 '#title' => 'Select your class or interface',
46 '#description' => 'This class will be displayed as part of a Class Diagram.',
47 '#options' => $kinds,
48 '#required' => TRUE,
49 '#multiple' => TRUE,
50 '#size' => 20,
51 '#default_value' => $types,
52 );
53 $form['submit'] = array(
54 '#type' => 'submit',
55 '#value' => 'Submit',
56 );
57 $form['result'] = array(
58 '#title' => 'Classes',
59 '#prefix' => '<div>',
60 '#markup' => graphapi_class_build($types),
61 '#suffix' => '</div>',
62 );
63 return $form;
64 }
65
66 function graphapi_class_form_submit(&$form, &$form_state) {
67 $class = $form_state['values']['class'];
68 $list = join('+', $class);
69 $options = array(
70 'absolute' => TRUE,
71 'query' => array(
72 'ids' => array_keys($class),
73 ),
74 );
75 $url = url('admin/config/system/graphapi/uml', $options);
76 $form_state['redirect'] = array(
77 $url,
78 $options,
79 );
80 drupal_redirect_form($form_state);
81 }
82
83 /**
84 * Generate a hopefully consistent cache id
85 *
86 * We assume $item when recursive is properly sorted
87 *
88 * @param array $item
89 * @return string
90 */
91 function _graphapi_class_get_cid(array $item) {
92 ksort($item);
93 $cid = serialize($item);
94 $cid = md5($cid);
95 return $cid;
96 }
97
98 function _graphapi_class_get_cache($cid) {
99 return cache()->get($cid);
100 }
101
102 function _graphapi_class_cache_set($cid, $data) {
103 return cache()->set($cid, $data);
104 }
105
106 function _graphapi_class_services() {
107 $yaml_discovery = new \Drupal\Component\Discovery\YamlDiscovery('services', \Drupal::moduleHandler()->getModuleDirectories());
108 $yaml = $yaml_discovery->findAll();
109 $classes = array();
110 foreach ($yaml as $module => $services) {
111 //dsm($services, $module);
112 foreach ($services['services'] as $key => $value) {
113 $classes[$value['class']] = "$module:$key";
114 }
115 }
116 //dsm($classes, __FUNCTION__);
117 return array_keys($classes);
118 }
119
120 /**
121 * Generates a UML CLass Diagram for the given format.
122 *
123 * @param array $classes
124 * List of class and interface names
125 * @param array $config
126 * Settings for both builder as the rendering
127 * generate-script : the dot textual output
128 * generate-image: generate and svg or other image format
129 * only-self : see ClassDiagramBuilder for more info
130 * only-public : see ClassDiagramBuilder
131 * show-constants : see ClassDiagramBuilder
132 * add-parents : see ClassDiagramBuilder
133 *
134 * @return string
135 * The output is rendered as a group of classes seperated by a HR
136 * TODO: is a HR the best seperation.
137 *
138 * @see ClassDiagramBuilder
139 */
140 function graphapi_class_build_class(array $classes = array(), array $config = array()) {
141 graphapi_loader();
142
143 $config += array(
144 'generate-script' => FALSE,
145 'generate-image' => TRUE,
146 // 'type' => 'svg',
147 );
148 $options = array(
149 'script' => $config['generate-script'],
150 'render' => $config['generate-image'],
151 );
152 unset($config['generate-script']);
153 unset($config['generate-image']);
154
155 // $cid = _graphapi_class_get_cid($classes, $script_only);
156 // if ($o = _graphapi_class_get_cache($cid)) {
157 // return $o->data;
158 // }
159 $result = array();
160
161 $g = new Graph();
162 $loader = new ClassDiagramBuilder($g);
163 foreach ($config as $option => $value) {
164 $loader->setOption($option, $value);
165 }
166 // Reduce output.
167 //$loader->setOption('only-self', TRUE);
168 //$loader->setOption('only-public', TRUE);
169 //$loader->setOption('show-constants', FALSE);
170 $messages[] = array();
171 try {
172 $i=0;
173 foreach ($classes as $class) {
174 if ($i++ % 100 == 0) {
175 watchdog("graphapi_class", "Progressing to $i items.");
176 }
177 // Do not try to add an already added class (through reflection)
178 if (!$g->hasVertex($class)) {
179 try {
180 $loader->createVertexClass($class);
181 }
182 catch (Exception $exc) {
183 $message = $exc->getMessage();
184 $messages[$class] = $message;
185 }
186 }
187 }
188 }
189 catch (Exception $exc) {
190 return $exc->getMessage();
191 }
192
193 $render_options = array(
194 'graphapi' => $options,
195 );
196 // $options = array(
197 // 'graphapi' => array(
198 // 'script' => FALSE,
199 // 'render' => TRUE,
200 // )
201 // );
202 foreach ($loader->createGraphsComponents() as $graph) {
203 $result[] = graphapi_render($graph, $render_options);
204 }
205 $total = join("<hr />", $result);
206 //_graphapi_class_cache_set($cid, $total);
207 return $total;
208 }
209
210 function graphapi_class_cron() {
211 $last_run = _graphapi_class_get_cache(__FUNCTION__);
212 // Only generate every hour
213 if ($last_run && $last_run->data + 3600 > time()) {
214 return;
215 }
216 _graphapi_class_cache_set(__FUNCTION__, time());
217 _graphapi_class_generate_run();
218 }
219
220 function _graphapi_class_generate_run() {
221 graphapi_loader();
222 $queue = Drupal::queue('graphapi_class_generator');
223 $classes = graphapi_class_get_all();
224 $count = count($classes);
225 $scheduled = 0;
226 $options = array(
227 'add-parents' => 1,
228 );
229
230 foreach ($classes as $path => $class) {
231 $item = array(
232 'classes' => array($class),
233 'path' => $path,
234 'options' => $options,
235 );
236
237 $cid = _graphapi_class_get_cid($item);
238 $cached_item = _graphapi_class_get_cache($cid);
239 if ($cached_item === FALSE) {
240 $scheduled++;
241 $queue->createItem($item);
242 }
243 }
244 watchdog('graphapi_class', 'Scheduled %scheduled out of %count classes and interfaces.', array('%scheduled' => $scheduled, '%count' => $count));
245 }
246
247 /**
248 * Implements hook_queue_info().
249 */
250 function graphapi_class_queue_info() {
251 $queues['graphapi_class_generator'] = array(
252 'title' => t('Generate all UML Graphs'),
253 'worker callback' => 'graphapi_class_generator_worker',
254 'cron' => array(
255 'time' => 60,
256 ),
257 );
258 return $queues;
259 }
260
261 /**
262 * Queue worker callback.
263 */
264 function graphapi_class_generator_worker($item) {
265 $path_prefix = "graphapi/uml/classdiagrams/";
266 if (isset($item['classes'])) {
267 $classes = $item['classes'];
268 }
269 else {
270 $classes = array($item['class']);
271 }
272 $path = 'public://' . $path_prefix . $item['path'] . '.html';
273
274 $options = $item['options'];
275
276 $result = graphapi_class_build_class($classes, $options);
277
278 $cid = _graphapi_class_get_cid($classes);
279 _graphapi_class_cache_set($cid, $result);
280
281 @drupal_mkdir(dirname($path), null, TRUE);
282 file_put_contents($path, $result);
283 }
284
285 function graphapi_class_get_all() {
286 static $classes;
287 if (!isset($classes)) {
288 $classes = &drupal_static(__FUNCTION__, array());
289 }
290 else {
291 return $classes;
292 }
293 graphapi_loader();
294 $loader = drupal_classloader();
295 // Directories to look for
296 $prefixes = $loader->getPrefixes();
297
298 $root = $_SERVER["DOCUMENT_ROOT"];
299
300 $skip_prefixes = array(
301 'Zend',
302 'Symfony',
303 'Twig', // Generates error: Class Twig/Autoloader.php does not exist
304 );
305 foreach ($prefixes as $prefix => $dirs) {
306 $skip = FALSE;
307 foreach ($skip_prefixes as $test_prefix) {
308 if (strpos($prefix, $test_prefix) === 0) {
309 $skip = TRUE;
310 }
311 }
312 if ($skip) {
313 watchdog('graphapi_class', "Skipping from exclude listfor '%prefix'", array('%prefix' => $prefix), WATCHDOG_NOTICE);
314 continue;
315 }
316
317 foreach ($dirs as $dir) {
318
319 $class_finder = new Finder();
320 try {
321 // @see http://symfony.com/doc/current/components/finder.html
322 // We scan each directory individually due to
323 // - https://drupal.org/node/2176683 : We register invalid components to composer.json and in other ways
324 $class_finder->files()->name('*.php')->in($dir);
325 }
326 catch (Exception $exc) {
327 watchdog('graphapi_class', "Exception while scanning for '%prefix' directory '%dir'", array('%prefix' => $prefix, '%dir' => $dir), WATCHDOG_WARNING);
328 continue;
329 }
330 foreach ($class_finder as $file) {
331 // Path relative to it's scan dir
332 $real_path = $file->getRealPath();
333 $path = str_replace($root, '', $real_path);
334 $class = $file->getRelativePathname();
335 // scrub .php and toggle /
336 $class = preg_replace("/.php$/", '', $class);
337 $class = str_replace('/', '\\', $class);
338 $classes[$path] = $class;
339 }
340 }
341 }
342
343 watchdog('graphapi_class', "Found %count classes and interfaces", array('%count' => count($classes)), WATCHDOG_INFO);
344
345 return $classes;
346 }
347
348 /**
349 * Implements hook_help().
350 */
351 function graphapi_class_help($path, $arg) {
352 if ($path == 'admin/help#graphapi_class') {
353 $output = '';
354 $output .= '<h3>' . t('About') . '</h3>';
355 $output .= '<p>' . t('The Graph API sub module graphapi_class generates UML Diagrams') . '</p>';
356 $output .= '<h3>' . t('Uses') . '</h3>';
357 $output .= '<dl>';
358 $output .= '<dt>' . t('Using input filter Class Diagram') . '</dt>';
359 $output .= '<dd>' . 'FilterTrivialGraphFormat::help(TRUE)' . '</dd>';
360 $output .= '</dl>';
361 return $output;
362 }
363 }