#!/usr/bin/env php When walking the dependency graph, limit it to this depth. For example, {$argv[0]} -f -d 1 MyClass would show only the files that directly depend on MyClass. -l, --file-list -q, --quick Uses Phan's --quick mode -p, --progress-bar Show progress bar -h, --help This help If no filenames or classnames are provided, it will generate the full dependency tree. Note that this tool will read your local .phan/config.php and pick out the list of files to scan/not scan from there. Or, you can provide it with a file list. Examples: {$argv[0]} -f src/Phan/PluginV3/PluginAwarePreAnalysisVisitor.php {$argv[0]} -c -g '\Phan\PluginV3\PluginAwarePreAnalysisVisitor' | dot -Tpng > graph.png {$argv[0]} -c -d 2 -g '\Phan\Language\Type\ClassStringType' | dot -Kfdp -Tpng > graph.png EOT; exit($status); } call_user_func(static function () : void { global $argv; $tool_dir = dirname($argv[0]); $depth = 0; $cmd = ''; $graph_flags = 0; $graph_file = ''; $options = getopt( "cfjgmhpqd:l:i:", [ 'import', 'json', 'graph', 'graphml', 'ignore-static', 'ignore-new', 'hide-labels', 'find-classes', 'find-files', 'file-list:', 'progress-bar', 'depth:', 'help', ], $optind ); if (isset($options['find-classes'])) { $options['c'] = false; } if (isset($options['find-files'])) { $options['f'] = false; } if (isset($options['depth'])) { $options['d'] = false; } if (isset($options['json'])) { $options['j'] = false; } if (isset($options['import'])) { $options['i'] = false; } $mode = null; if (isset($options['c'])) { if (isset($options['f'])) { echo "ERROR: Cannot pass both -c and -f\n"; pdep_usage(1); } $mode = 'class'; } elseif (isset($options['f'])) { $mode = 'file'; } if (isset($options['d'])) { if (empty($mode)) { echo "ERROR: You must specify either -c or -f\n"; pdep_usage(1); } } if (isset($options['h']) || isset($options['help'])) { pdep_usage(0); return; } require_once __DIR__ . '/../src/Phan/Bootstrap.php'; $cli_builder = new CLIBuilder(); foreach ($options as $opt => $value) { switch ($opt) { case 'j': case 'json': $cmd = 'json'; break; case 'g': case 'graph': $cmd = "graph"; break; case 'm': case 'graphml': $cmd = "graphml"; break; case 'ignore-static': $graph_flags |= \PDEP_IGNORE_STATIC; break; case 'ignore-new': $graph_flags |= \PDEP_IGNORE_NEW; break; case 'hide-labels': $graph_flags |= \PDEP_HIDE_LABELS; break; case 'p': case 'progress-bar': $cli_builder->setOption('progress-bar'); break; case 'q': case 'quick': $cli_builder->setOption('quick'); break; case 'd': case 'depth': $depth = filter_var($value, FILTER_VALIDATE_INT); if ($depth === false) { echo "ERROR: Invalid depth '$value' (expected int)\n"; pdep_usage(1); } break; case 'l': case 'file-list': // @phan-suppress-next-line PhanPossiblyNullTypeArgument $cli_builder->setOption('file-list', $value); break; case 'i': case 'import': $cli_builder->setOption('no-progress-bar'); $graph_file = (string)$value; break; } } if ($cmd === 'json' && $depth === 0) { $mode = ''; } // Args for PDEP $arg_string = implode(' ', array_slice($argv, $optind)); if (empty($arg_string) && $depth) { echo "ERROR: You must specify a starting node when specifying a depth\n"; pdep_usage(1); } $cwd = \getcwd(); $cli_builder->setOption('allow-polyfill-parser'); $cli_builder->setOption('processes', '1'); $cli_builder->setOption('plugin', dirname($tool_dir) . '/src/Phan/Plugin/Internal/DependencyGraphPlugin.php'); $cli_builder->setOption('config-file', "$cwd/.phan/pdep_config.php"); // @phan-suppress-next-line PhanThrowTypeAbsentForCall $cli = $cli_builder->build(); $putenv = static function (string $key, string $value) : void { putenv("$key=$value"); $_ENV[$key] = $value; }; $putenv("PDEP_CMD", $cmd); $putenv("PDEP_MODE", (string)$mode); $putenv("PDEP_DEPTH", (string)$depth); $putenv("PDEP_ARGS", $arg_string); $putenv("PDEP_GRAPH_FLAGS", (string)$graph_flags); // Generate codebase info after parsing configs (e.g. included_extension_subset) $code_base = require(__DIR__ . '/../src/codebase.php'); if (isset($options['i'])) { $data = @file_get_contents($graph_file); if (!is_string($data)) { echo "Unable to read graph file '$graph_file'\n"; exit(1); } $cached_graph = @json_decode($data, true); if (!is_array($cached_graph)) { echo "Invalid JSON contents of graph file '$graph_file': " . (json_last_error() !== JSON_ERROR_NONE ? json_last_error_msg() : 'expected array, got ' . gettype($cached_graph)) . "\n"; exit(1); } require_once __DIR__ . '/../src/Phan/Plugin/Internal/DependencyGraphPlugin.php'; (new \Phan\Plugin\Internal\DependencyGraphPlugin)->processGraph($cached_graph); } else { // @phan-suppress-next-line PhanThrowTypeAbsentForCall Phan::analyzeFileList($code_base, /** @return string[] */ static function () use($cli) : array { return $cli->getFileList(); }); } });