const regex = /(\s+)(?:abstract\s+|final\s+|private\s+|protected\s+|public\s+)?(?:static\s+)?(function)\s+(\w+)\s*(\((?>[^()]+|(?R))*\))|({(?>[^{}]+|(?R))*})/igms;
// Alternative syntax using RegExp constructor
// const regex = new RegExp('(\\s+)(?:abstract\\s+|final\\s+|private\\s+|protected\\s+|public\\s+)?(?:static\\s+)?(function)\\s+(\\w+)\\s*(\\((?>[^()]+|(?R))*\\))|({(?>[^{}]+|(?R))*})', 'igms')
const str = `<?php declare(strict_types=1);
namespace PHPAstVisualizer;
use phpDocumentor\\GraphViz\\Edge as GraphEdge;
use phpDocumentor\\GraphViz\\Graph;
use phpDocumentor\\GraphViz\\Node as GraphNode;
class Options {
private \$options = [
'graph' => [],
'node' => ['shape' => 'rect'],
'edge' => [],
'childEdge' => ['style' => 'dashed','arrowhead' => 'empty'],
];
private \$name;
}
public function __construct(string \$name, array \$options = []) {
\$this->name = \$name;
\$this->graphOptions = array_merge(\$this->options, \$options);
}
public function getName(): string {
return \$this->name;
}
public function graph(Graph \$graph) {
foreach (\$this->options['graph'] as \$name => \$value) {
\$graph->{'set' . \$name}(\$value);
}
}
public function node(GraphNode \$node) {
foreach (\$this->options['node'] as \$name => \$value) {
\$node->{'set' . \$name}(\$value);
}
}
public function childEdge(GraphEdge \$edge) {
\$this->edge(\$edge);
foreach (\$this->options['childEdge'] as \$name => \$value) {
\$edge->{'set' . \$name}(\$value);
}
}
public function edge(GraphEdge \$edge) {
foreach (\$this->options['edge'] as \$name => \$value) {
\$edge->{'set' . \$name}(\$value);
}
}
}
/**
* PHP file with an autoload function which will be included into the
* sandbox of the InstantSVC CodeAnalyzer
* @var string
*/
protected \$autoloadFile = '';
/**
* Sets the output driver and initializes
* @param CallgraphDriver \$driver output driver to use
* @return PHPCallGraph
*/
public function __construct(CallgraphDriver \$driver = null) {
if (\$driver != null) {
\$this->driver = \$driver;
} else {
\$this->driver = new TextDriver();
}
\$functions = get_defined_functions();
\$this->internalFunctions = \$functions['internal'];
// List of PHP keywords which could be followed by an opening parenthis
// taken from PHP Manual (http://www.php.net/reserved_keywords)
\$this->internalKeywords = array(
'array', 'declare', 'die', 'echo', 'elseif', 'empty', 'eval',
'exit', 'for', 'foreach', 'global', 'if', 'include', 'include_once',
'isset', 'list', 'print', 'require', 'require_once', 'return',
'switch', 'unset', 'while', 'catch', 'or', 'and', 'xor', 'new Exception');
\$this->constants = array_keys(get_defined_constants());
//TODO: provide setter for this
//\$this->ignoreList = array('PHPCallGraph::setDriver', 'PHPCallGraph::__construct', 'PHPCallGraph::setShowExternalCalls', 'PHPCallGraph::setShowInternalFunctions', 'PHPCallGraph::save', 'PHPCallGraph::__toString');
}
public function setDriver(CallgraphDriver \$driver = null) {
if (\$driver != null) {
\$this->driver = \$driver;
}
}
/**
* Enable or disable printing of debug information
* @param boolean \$enabled
*/
public function setDebug(\$enabled = true) {
\$this->debug = \$enabled;
}
protected function debug(\$string) {
if (\$this->debug) {
print "||PHPCallGraph| ".\$string."\\n";
}
}
protected function info(\$string) {
if (\$this->showInfo) {
print "||PHPCallGraph| ".\$string."\\n";
}
}
protected function warning(\$string) {
if (\$this->showWarnings) {
print "||PHPCallGraph* *WARNING* ".\$string."\\n";
}
}
/**
* Sets a PHP file with an autoload function which will be included into
* the sandbox of the InstantSVC CodeAnalyzer
* @param string \$filename Name of a PHP file with an autoload function
* @return boolean success
*/
public function setAutoloadFile(\$filename) {
\$returnValue = false;
if (!empty(\$filename) and is_file(\$filename) and is_readable(\$filename)) {
\$this->autoloadFile = \$filename;
\$returnValue = true;
} else {
//TODO: throw exception
}
return \$returnValue;
}
public function setShowExternalCalls(\$boolean = true) {
\$this->showExternalCalls = \$boolean;
}
public function setShowInternalFunctions(\$boolean = true) {
\$this->showInternalFunctions = \$boolean;
}
public function collectFileNames(array \$filesOrDirs, \$recursive = false) {
\$files = array();
foreach (\$filesOrDirs as \$fileOrDir) {
if (is_file(\$fileOrDir)) {
\$files[] = \$fileOrDir;
} elseif (is_dir(\$fileOrDir)) {
\$globbed = glob("\$fileOrDir/*");
if (\$recursive) {
\$files = array_merge(\$files, \$this->collectFileNames(\$globbed, true));
} else {
foreach(\$globbed as \$path) {
if (is_file(\$path)) {
\$files[] = \$path;
}
}
}
}
}
return \$files;
}
public function parse(array \$filesOrDirs, \$recursive = false) {
\$files = \$this->collectFileNames(\$filesOrDirs, \$recursive);
if (\$this->debug) {
var_dump(\$files);
}
\$ca = new iscCodeAnalyzer(null);
\$ca->setDebug(\$this->debug);
\$ca->setAutoloadFile(\$this->autoloadFile);
\$ca->inspectFiles(\$files);
\$this->codeSummary = \$ca->getCodeSummary();
\$this->analyseCodeSummary();
}
public function parseFile(\$file) {
\$ca = new iscCodeAnalyzer(null);
\$ca->setDebug(\$this->debug);
\$ca->setAutoloadFile(\$this->autoloadFile);
\$ca->inspectFiles(array(\$file));
\$this->codeSummary = \$ca->getCodeSummary();
\$this->analyseCodeSummary();
}
public function parseDir(\$dir = '.') {
\$ca = new iscCodeAnalyzer(\$dir);
\$ca->setDebug(\$this->debug);
\$ca->setAutoloadFile(\$this->autoloadFile);
\$ca->collect();
\$this->codeSummary = \$ca->getCodeSummary();
\$this->analyseCodeSummary();
}
public function analyseCodeSummary() {
\$this->buildLookupTables();
//TODO: analyze code in the global scope
// currently a workarround is to manually wrap such code
// in a dummy function called dummyFunctionForFile_filename_php()
// analyze functions
if (!empty(\$this->codeSummary['functions'])) {
foreach (\$this->codeSummary['functions'] as \$functionName => \$function) {
\$this->parseMethodBody(
'-',
\$functionName,
array(),
array(),
\$function['file'],
\$function['startLine'],
\$function['endLine']
);
}
}
// analyze classes
if (!empty(\$this->codeSummary['classes'])) {
foreach (\$this->codeSummary['classes'] as \$className => \$class) {
/*
echo \$className, "\\n";
var_export(\$class);
echo "\\n\\n";
//*/
if (!empty(\$class['methods'])) {
\$propertyNames = array_keys(\$class['properties']);
\$methodNames = array_keys(\$class['methods']);
//var_export(\$propertyNames);
//var_export(\$methodNames);
foreach (\$class['methods'] as \$methodName => \$method) {
\$this->parseMethodBody(
\$className,
\$methodName,
\$propertyNames,
\$methodNames,
\$class['file'],
\$method['startLine'],
\$method['endLine']
);
}
}
}
}
}
protected function buildLookupTables() {
if (!empty(\$this->codeSummary['classes'])) {
foreach (\$this->codeSummary['classes'] as \$className => \$class) {
//currently unused
/*
if (!empty(\$class['properties'])) {
foreach (\$class['properties'] as \$propertyName => \$property) {
\$this->propertyLookupTable[\$propertyName][] = \$className;
}
}
//*/
if (!empty(\$class['methods'])) {
foreach (\$class['methods'] as \$methodName => \$method) {
\$this->methodLookupTable[\$methodName][] = \$className;
}
}
}
}
}
/**
* @param string \$className
* @param string \$methodName
* @param array \$propertyNames
* @param array \$methodNames
* @param string \$file
* @param integer \$startLine
* @param integer \$endLine
*/
public function parseMethodBody(
\$className,
\$methodName,
Array \$propertyNames,
Array \$methodNames,
\$file,
\$startLine,
\$endLine
) {
if (\$className == '-') { // we are analyzing a function not a method
if (substr(\$methodName, 0, strlen('dummyFunctionForFile_')) == 'dummyFunctionForFile_') {
// the function has been introduced manually to encapsulate code in the global scope
\$callerName = str_replace('_', '.', substr(\$methodName, strlen('dummyFunctionForFile_'))) . '(File)';
} else {
\$callerName = \$methodName . \$this->generateParametersForSignature(\$this->codeSummary['functions'][\$methodName]['params']);
}
} else {
//TODO: visibilities
\$callerName = \$className . '::' . \$methodName . \$this->generateParametersForSignature(\$this->codeSummary['classes'][\$className]['methods'][\$methodName]['params']);
}
\$callerNameWithoutParameterList = substr(\$callerName, 0, strpos(\$callerName, '('));
if (!in_array(\$callerNameWithoutParameterList, \$this->ignoreList)) {
\$this->debug('phpCallGraph: analyzing ' . \$callerName);
\$offset = \$startLine - 1;
\$length = \$endLine - \$startLine + 1;
// obtain source code
\$memberCode = implode('', array_slice(file(\$file), \$offset, \$length));
\$memberCode = "<?php\\nclass \$className {\\n" . \$memberCode . "}\\n?>\\n";
//echo \$memberCode;
\$this->debug("= Analyzing \$callerName =");
\$this->info(" defined in \$file on line \$offset");
\$this->driver->startFunction(\$offset, \$file, \$callerName, \$memberCode);
\$insideDoubleQuotedString = false;
\$lineNumber = \$offset - 1;
\$blocksStarted = 0;
// parse source code
\$tokens = token_get_all(\$memberCode);
/*
if (\$methodName == '__construct') {
print_r(\$tokens);
}
//*/
//TODO: implement a higher level API for working with PHP parser tokens (e.g. TokenIterator)
foreach (\$tokens as \$i => \$token) {
//TODO: obtain method signature directly from the source file
if (is_array(\$token)) {
\$lineNumber+= substr_count(\$token[1], "\\n");
}
/*
if (count(\$token) == 3) {
echo "\\t", token_name(\$token[0]), "\\n";
echo "\\t\\t", \$token[1], "\\n";
} else {
echo "\\t", \$token[0], "\\n";
}
//*/
// skip call analysis for the method signature
if (\$blocksStarted < 2) {
// method body not yet started
if (\$token[0] == '{') {
++\$blocksStarted;
}
continue;
}
if (!\$insideDoubleQuotedString and \$token == '"') {
\$insideDoubleQuotedString = true;
} elseif (\$insideDoubleQuotedString and \$token == '"') {
\$insideDoubleQuotedString = false;
} elseif (!\$insideDoubleQuotedString and \$token != '"') {
if (\$token[0] == T_STRING
//and (\$token[1] != \$className or \$tokens[\$i - 2][0] == T_NEW )
//and \$token[1] != \$methodName
) {
if (
!in_array(\$token[1], \$propertyNames) //TODO: property name equals name of a function or method
and !in_array(\$token[1], \$this->constants) //TODO: constant name equals name of a function or method
and \$token[1] != 'true'
and \$token[1] != 'false'
and \$token[1] != 'null'
) {
\$previousPreviousPreviousToken = \$tokens[ \$i - 3 ];
\$previousPreviousToken = \$tokens[ \$i - 2 ];
\$previousToken = \$tokens[ \$i - 1 ];
\$nextToken = \$tokens[ \$i + 1 ];
\$tokenAfterNext = \$tokens[ \$i + 2 ];
\$this->info(\$this->getTokenValues(\$tokens, \$i));
if (\$nextToken[0] == T_DOUBLE_COLON) {
// beginning of a call to a static method
//nop
continue;
} elseif (
(
\$tokens[ \$i - 4][0] == T_CATCH
and \$previousPreviousPreviousToken[0] == T_WHITESPACE
and \$previousPreviousToken == '('
and \$previousToken[0] == T_WHITESPACE
)
or
(
\$previousPreviousPreviousToken[0] == T_CATCH
and \$previousPreviousToken[0] == T_WHITESPACE
and \$previousToken == '('
)
or
(
\$previousPreviousToken[0] == T_CATCH
and \$previousToken == '('
)
){
// catch block
continue;
} elseif (\$previousPreviousToken[0] == T_NEW){
\$this->debug('Found object creation with new operator');
if (!\$this->showExternalCalls) {
continue;
}
\$calleeClass = \$token[1];
if (isset(\$this->codeSummary['classes'][\$calleeClass])) {
// find constructor method
if (isset(\$this->codeSummary['classes'][\$calleeClass]['methods']['__construct'])) {
\$calleeName = "\$calleeClass::__construct"
. \$this->generateParametersForSignature(\$this->codeSummary['classes'][\$calleeClass]['methods']['__construct']['params']);
} elseif (isset(\$this->codeSummary['classes'][\$calleeClass]['methods'][\$calleeClass])) {
\$calleeName = "\$calleeClass::\$calleeClass"
. \$this->generateParametersForSignature(\$this->codeSummary['classes'][\$calleeClass]['methods'][\$calleeClass]['params']);
} else {
\$calleeName = "\$calleeClass::__construct()";
}
\$calleeFile = \$this->codeSummary['classes'][\$calleeClass]['file'];
} else {
// TODO: decide how this case should be handled (could be a PEAR class or a class of a PHP extension, e.g. GTK)
//if (\$this->showInternalFunctions)
\$calleeName = "\$calleeClass::__construct()"; // TODO: it could also be \$calleeClass::\$calleeClass() implemented in PHP4-style, however if we don't have the code, we can't now
\$calleeFile = '';
}
\$this->info(\$this->getTokenValues(\$tokens, \$i));
\$this->recordVariableAsType(\$calleeClass, \$tokens[\$i-6][1]);
} elseif (
(
isset(\$previousPreviousToken[1]) and \$previousPreviousToken[1] == '\$this'
and \$previousToken[0] == T_OBJECT_OPERATOR
and in_array(\$token[1], \$methodNames)
)
or
(
isset(\$previousPreviousToken[1]) and \$previousPreviousToken[1] == 'self'
and \$previousToken[0] == T_DOUBLE_COLON
and in_array(\$token[1], \$methodNames)
)
){
\$this->info('internal method call (\$this-> and self:: and \$className::)');
\$calleeName = "\$className::{\$token[1]}" . \$this->generateParametersForSignature(\$this->codeSummary['classes'][\$className]['methods'][\$token[1]]['params']);
\$calleeFile = \$file;
} elseif (\$previousToken[0] == T_OBJECT_OPERATOR) {
\$this->debug('External method call or property access');
//TODO: what if a object holds another instance of its class
if (!\$this->showExternalCalls) {
continue;
}
if (\$nextToken == '(' or (\$nextToken[0] == T_WHITESPACE and \$tokenAfterNext == '(')) {
\$calleeName = \$token[1];
\$this->debug("Calling for \$calleeName");
\$variable = \$tokens[\$i-2][1];
\$this->debug("Variable = \$variable");
\$calleeClass = \$this->lookupTypeForVariable(\$variable);
\$this->debug('found as ' . \$calleeClass);
if (\$calleeClass) {
\$calleeParams = \$this->generateParametersForSignature(
\$this->codeSummary['classes'][\$calleeClass]['methods'][\$calleeName]['params']
);
\$calleeFile = \$this->codeSummary['classes'][\$calleeClass]['file'];
} else {
if (
isset(\$this->methodLookupTable[\$calleeName])
and count(\$this->methodLookupTable[\$calleeName]) == 1
) {
// there is only one class having a method with this name
// SMELL: but if the user only registers part of a system the only one hit
// is not necessarily valid.
\$calleeClass = \$this->methodLookupTable[\$calleeName][0];
if (isset(\$this->codeSummary['classes'][\$calleeClass])) {
\$calleeParams = \$this->generateParametersForSignature(
\$this->codeSummary['classes'][\$calleeClass]['methods'][\$calleeName]['params']
);
\$calleeFile = \$this->codeSummary['classes'][\$calleeClass]['file'];
\$this->info("RECORDING CLASS OF \$previousPreviousToken[1] VARIABLE to be \$calleeClass\\n");
\$this->info(\$this->getTokenValues(\$tokens, \$i));
} else {
\$this-warning("calleeClass is unset");
\$calleeParams = null;
\$calleeFile = null;
}
} else {
\$numEntries = count(\$this->methodLookupTable[\$calleeName]);
if (\$numEntries == 0) {
\$this->warning("method \$calleeName was called, but I have no record for that");
} else {
\$this->warning("I have \$numEntries for \$calleeName!");
}
\$this->info(\$this->getTokenValues(\$tokens, \$i));
\$calleeClass = '';
\$calleeParams = '()';
\$calleeFile = '';
}
}
\$calleeName = "\$calleeClass::\$calleeName\$calleeParams";
} else {
\$this->info("Property access");
continue;
}
} elseif (\$previousToken[0] == T_DOUBLE_COLON){
\$this->debug("static external method call");
if (!\$this->showExternalCalls) {
continue;
}
if (\$nextToken != '(' and !(\$nextToken[0] == T_WHITESPACE and \$tokenAfterNext == '(')) {
// constant access
continue;
}
\$calleeClass = \$previousPreviousToken[1];
\$calleeMethod = \$token[1];
\$calleeFile = '';
\$calleeParams = '()';
// parent::
if (\$calleeClass == 'parent' and !empty(\$this->codeSummary['classes'][\$className]['parentClass'])) {
\$calleeClass = \$this->codeSummary['classes'][\$className]['parentClass'];
}
if (isset(\$this->codeSummary['classes'][\$calleeClass])) {
\$calleeFile = \$this->codeSummary['classes'][\$calleeClass]['file'];
if (isset(\$this->codeSummary['classes'][\$calleeClass]['methods'][\$calleeMethod]['params'])) {
\$calleeParams = \$this->generateParametersForSignature(\$this->codeSummary['classes'][\$calleeClass]['methods'][\$calleeMethod]['params']);
}
}
\$calleeName = "\$calleeClass::\$calleeMethod\$calleeParams";
//TODO: handle self::myMethod(); \$className::myMethod(); here => abolish internal method call case
} else {
\$calledFunction = \$token[1];
\$calleeFile = '';
\$calleeParams = '()';
\$this->info("Function call: ".\$calledFunction);
if (in_array(\$calledFunction, \$this->internalFunctions)) {
if (!\$this->showInternalFunctions) {
continue;
}
} else {
if (!\$this->showExternalCalls) {
continue;
}
if (isset(\$this->codeSummary['functions'][\$calledFunction])) {
\$calleeFile = \$this->codeSummary['functions'][\$calledFunction]['file'];
if (isset(\$this->codeSummary['functions'][\$calledFunction]['params'])) {
\$calleeParams = \$this->generateParametersForSignature(\$this->codeSummary['functions'][\$calledFunction]['params']);
}
}
}
\$calleeName = \$calledFunction . \$calleeParams;
}
\$this->debug("---> \$calleeName called from line \$lineNumber and defined in \$calleeFile");
\$this->driver->addCall(\$lineNumber, \$calleeFile, \$calleeName);
}
}
} else {
//TODO: parse calls inside double quoted strings
\$this->info(' ignoring code inside ""');
}
}
\$this->debug('== endFunction ==');
\$this->driver->endFunction();
}
}
}
public function generateParametersForSignature(\$parameters) {
\$result = '(';
if (!empty(\$parameters)) {
foreach(\$parameters as \$parameterName => \$parameter) {
if (\$parameter['byReference']) {
\$result.= '&';
}
\$result.= '\$' . \$parameterName . ', ';
}
\$result = substr(\$result, 0, -2);
}
\$result.= ')';
return \$result;
}
}
public function __toString() {
return \$this->driver->__toString();
}
public function save(\$file) {
return file_put_contents(\$file, \$this->__toString());
}
protected function getTokenValues(\$tokens, \$i) {
\$width = 10;
\$pad = ' ';
\$out = "\\n";
\$start = -5;
\$end = 4;
\$headerLine = '';
\$rowLine = '';
\$tokenLine = '';
for (\$j = \$start; \$j <= \$end; \$j++) {
\$n = (\$i + \$j);
\$currentToken = \$tokens[\$n];
\$tokenType = '';
\$cell = '';
\$header = '';
if (is_array(\$currentToken)) {
\$mainValue = \$currentToken[1];
\$mainValue = str_replace("\\n", '\\n', \$mainValue);
\$mainValue = str_replace("\\t", '\\t', \$mainValue);
if (\$mainVlaue = '') {
\$mainValue = 'nil';
}
\$cell = \$mainValue;
\$tokenType = token_name(\$currentToken[0]);
} else {
\$cell = '"'.\$currentToken.'"';
}
\$cell = \$cell;
\$cell = str_pad(\$cell, \$width, \$pad);
\$tokenType = str_pad(substr(\$tokenType,0,\$width), \$width, \$pad);
\$header = '['.\$j.']';
\$header = str_pad(\$header, \$width, \$pad);
\$headerLine .= \$header.' ';
\$rowLine .= \$cell.' ';
\$tokenLine .= \$tokenType.' ';
}
\$out .= \$headerLine."\\n";
\$out .= \$rowLine."\\n";
\$out .= \$tokenLine."\\n";
return \$out;
}
protected \$variableTypes = array();
protected function recordVariableAsType(\$class, \$variable) {
\$this->debug("RECORDING \$variable AS TYPE \$class");
\$this->variableTypes[\$variable] = \$class;
}
/**
* Returns a recorded type for a given variable, unless the variable is named '\$self'
* in which case returns the class name.
* If no match is found returns null.
*/
protected function lookupTypeForVariable(\$variable) {
\$recordedType = \$this->variableTypes[\$variable];
if (\$recordedType) {
\$type = \$recordedType;
} else {
\$this->warning("No recorded type for \$variable");
\$type = null;
}
\$this->info('LOOKUP FOR '.\$variable.' returns '.\$type);
return \$type;
}
}
?>
<?php
/**
* Implementation of a call graph generation strategy wich renders a graph with
* dot.
*
* PHP version 5
*
* This file is part of phpCallGraph.
*
* PHPCallGraph is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* PHPCallGraph is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package PHPCallGraph
* @author Falko Menge <fakko at users dot sourceforge dot net>
* @copyright 2007-2009 Falko Menge
* @license http://www.gnu.org/licenses/gpl.txt GNU General Public License
*/
// reduce warnings because of PEAR dependency
error_reporting(E_ALL ^ E_NOTICE);
require_once 'CallgraphDriver.php';
require_once 'Image/GraphViz.php';
/**
* Implementation of a call graph generation strategy wich renders a graph with
* dot.
*/
class GraphVizDriver implements CallgraphDriver {
protected \$outputFormat;
protected \$dotCommand;
protected \$useColor = true;
protected \$graph;
protected \$currentCaller = '';
protected \$internalFunctions;
}
/**
* @return CallgraphDriver
*/
public function __construct(\$outputFormat = 'png', \$dotCommand = 'dot') {
\$this->initializeNewGraph();
\$this->setDotCommand(\$dotCommand);
\$this->setOutputFormat(\$outputFormat);
\$functions = get_defined_functions();
\$this->internalFunctions = \$functions['internal'];
}
/**
* @return void
*/
public function reset() {
\$this->initializeNewGraph();
}
/**
* @return void
*/
protected function initializeNewGraph() {
\$this->graph = new Image_GraphViz(
true,
array(
'fontname' => 'Verdana',
'fontsize' => 12.0,
//'fontcolor' => 'gray5',
'rankdir' => 'LR', // left-to-right
)
);
\$this->graph->dotCommand = \$this->dotCommand;
}
/**
* Sets path to GraphViz/dot command
* @param string \$dotCommand Path to GraphViz/dot command
* @return void
*/
public function setDotCommand(\$dotCommand = 'dot') {
\$this->dotCommand = \$dotCommand;
\$this->graph->dotCommand = \$dotCommand;
}
/**
* Sets output format
* @param string \$outputFormat One of the output formats supported by GraphViz/dot
* @return void
*/
public function setOutputFormat(\$outputFormat = 'png') {
\$this->outputFormat = \$outputFormat;
}
/**
* Enables or disables the use of color
* @param boolean \$boolean True if color should be used
* @return void
*/
public function setUseColor(\$boolean = true) {
\$this->useColor = \$boolean;
}
/**
* @param integer \$line
* @param string \$file
* @param string \$name
* @return void
*/
public function startFunction(\$line, \$file, \$name, \$memberCode) {
\$this->addNode(\$name);
\$this->currentCaller = \$name;
}
/**
* @param integer \$line
* @param string \$file
* @param string \$name
* @return void
*/
public function addCall(\$line, \$file, \$name) {
\$this->addNode(\$name);
\$this->graph->addEdge(array(\$this->currentCaller => \$name));
}
/**
* @return void
*/
protected function addNode(\$name) {
\$nameParts = explode('::', \$name);
\$cluster = 'default';
\$label = \$name;
\$color = 'lavender'; //lightblue2, lightsteelblue2, azure2, slategray2
if (count(\$nameParts) == 2) { // method call
if (empty(\$nameParts[0])) {
\$cluster = 'class is unknown';
} else {
\$cluster = \$nameParts[0];
}
// obtain method name
\$label = \$nameParts[1];
}
// remove parameter list
\$label = substr(\$label, 0, strpos(\$label, '('));
if (count(\$nameParts) == 1) { // function call
if (in_array(\$label, \$this->internalFunctions)) { // call to internal function
\$cluster = 'internal PHP functions';
}
}
\$this->graph->addNode(
\$name,
array(
'fontname' => 'Verdana',
'fontsize' => 12.0,
//'fontcolor' => 'gray5',
'label' => \$label,
//'style' => 'rounded' . (\$this->useColor ? ',filled' : ''), // produces errors in rendering
'style' => (\$this->useColor ? 'filled' : 'rounded'),
'color' => (\$this->useColor ? \$color : 'black'),
'shape' => (\$this->useColor ? 'ellipse' : 'rectangle'),
),
\$cluster
);
//*
\$this->graph->addCluster(
\$cluster,
\$cluster,
array(
// 'style' => (\$this->useColor ? 'filled' : ''),
'color' => 'gray20',
// 'bgcolor' => '',
)
);
//*/
}
/**
* @return void
*/
public function endFunction() {
}
/**
* @return string
*/
public function __toString() {
return \$this->graph->fetch(\$this->outputFormat);
}
}
?>
`;
let m;
while ((m = regex.exec(str)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
// The result can be accessed through the `m`-variable.
m.forEach((match, groupIndex) => {
console.log(`Found match, group ${groupIndex}: ${match}`);
});
}
Please keep in mind that these code samples are automatically generated and are not guaranteed to work. If you find any syntax errors, feel free to submit a bug report. For a full regex reference for JavaScript, please visit: https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions