/** * Parse a single file * searching for namespaces, interfaces, traits, classes, * functions, constants, globals, includes and conditional code * * @param string $source Source filename * * @return void */ protected function scan($source) { $cache = PHP_CompatInfo_Cache::getInstance($this->options['cacheDriver'], $this->options); $cached = $cache->isCached($source); if ($cached) { $results = $cache->getCache($source); $this->excludes = $results['excludes']; $this->includes = $results['includes']; $this->versions = $results['versions']; $this->extensions = $results['extensions']; $this->namespaces = $results['namespaces']; $this->traits = $results['traits']; $this->interfaces = $results['interfaces']; $this->classes = $results['classes']; $this->functions = $results['functions']; $this->constants = $results['constants']; $this->globals = $results['globals']; $this->tokens = $results['tokens']; $conditions = $results['conditions']; } else { $this->excludes = array(); $this->includes = array(); $this->versions = array('4.0.0', ''); $this->extensions = array(); $this->namespaces = array(); $this->traits = array(); $this->interfaces = array(); $this->classes = array(); $this->functions = array(); $this->constants = array(); $this->globals = array(); $this->tokens = array(); $conditions = false; /** * @link http://www.php.net/manual/en/tokens.php * List of Parser Tokens */ $options = array('containers' => array('core' => 'internalFunctions', 'token' => 'tokens', 'glob' => 'globals'), 'properties' => array('interface' => array('keywords', 'methods', 'parent'), 'class' => array('keywords', 'methods', 'parent', 'interfaces'), 'function' => array('keywords', 'visibility', 'arguments'), 'require_once' => array(), 'require' => array(), 'include_once' => array(), 'include' => array())); $reflect = new PHP_Reflect($options); // internal functions $reflect->connect('T_STRING', 'PHP_CompatInfo_Token_STRING', array('PHP_CompatInfo_TokenParser', 'parseTokenString')); // user constants $reflect->connect('T_CONSTANT_ENCAPSED_STRING', 'PHP_CompatInfo_Token_CONSTANT_ENCAPSED_STRING', array('PHP_CompatInfo_TokenParser', 'parseTokenConstant')); // globals and super globals $reflect->connect('T_VARIABLE', 'PHP_Reflect_Token_VARIABLE', array('PHP_CompatInfo_TokenParser', 'parseTokenGlobals')); // language features / tokens $reflect->connect('T_CATCH', 'PHP_Reflect_Token_CATCH', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_CLONE', 'PHP_Reflect_Token_CLONE', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_INSTANCEOF', 'PHP_Reflect_Token_INSTANCEOF', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_THROW', 'PHP_Reflect_Token_THROW', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_TRY', 'PHP_Reflect_Token_TRY', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_FINALLY', 'PHP_Reflect_Token_FINALLY', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_HALT_COMPILER', 'PHP_Reflect_Token_HALT_COMPILER', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_GOTO', 'PHP_Reflect_Token_GOTO', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_UNSET_CAST', 'PHP_Reflect_Token_UNSET_CAST', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_INSTEADOF', 'PHP_Reflect_Token_INSTEADOF', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_YIELD', 'PHP_Reflect_Token_YIELD', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_OBJECT_OPERATOR', 'PHP_CompatInfo_Token_OBJECT_OPERATOR', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->connect('T_OPEN_SQUARE', 'PHP_CompatInfo_Token_OPEN_SQUARE', array('PHP_CompatInfo_TokenParser', 'parseTokenFeatures')); $reflect->scan($source); $this->_namespaces = $reflect->getNamespaces(PHP_Reflect::NAMESPACES_ALL); /** * @link http://www.php.net/manual/en/language.namespaces.php * Namespaces */ $namespaces = $reflect->getNamespaces(); if ($namespaces === null) { // adds (at least) global namespace $ns = '\\'; $namespaces = array($ns => array()); $defaultVersion = $reflect->isNamespaceWarning() ? '5.3.0' : '4.0.0'; } else { $ns = true; $defaultVersion = '5.3.0'; } $this->getInfo('namespaces', $defaultVersion, $namespaces, $source, $ns); /** * @link http://www.php.net/manual/en/language.control-structures.php * Control Structures */ $includes = $reflect->getIncludes(true); foreach ($includes as $key => $values) { $this->includes[$key] = array_keys($values); } foreach (array_keys($namespaces) as $ns) { /** * @link http://www.php.net/manual/en/language.oop5.traits.php * Traits */ $traits = $reflect->getTraits($ns); $this->getInfo('traits', '5.4.0', $traits, $source, $ns); /** * @link http://www.php.net/manual/en/language.oop5.interfaces.php * Object Interfaces */ $interfaces = $reflect->getInterfaces($ns); $this->getInfo('interfaces', '5.0.0', $interfaces, $source, $ns); /** * @link http://www.php.net/manual/en/language.oop5.php * Classes and Objects */ $classes = $reflect->getClasses($ns); $this->getInfo('classes', '4.0.0', $classes, $source, $ns); /** * @link http://www.php.net/manual/en/language.constants.php * Constants */ $constants = $reflect->getConstants(false, null, $ns); $this->getInfo('constants', '4.0.0', $constants, $source, $ns); /** * @link http://www.php.net/manual/en/functions.user-defined.php * User-defined functions * @link http://www.php.net/manual/en/functions.internal.php * Internal (built-in) functions */ $userFunctions = (array) $reflect->getFunctions($ns); $coreFunctions = (array) $reflect->getInternalFunctions($ns); $functions = array_merge_recursive($userFunctions, $coreFunctions); $this->getInfo('functions', '4.0.0', $functions, $source, $ns); // language features $tokens = (array) $reflect->offsetGet(array('tokens' => $ns)); $this->getInfo('tokens', '5.0.0', $tokens, $source, $ns); /** * @link http://www.php.net/manual/en/language.variables.superglobals.php * Superglobals */ $globals = (array) $reflect->getGlobals(true, null, $ns); foreach ($globals as $glob => $gdata) { foreach ($gdata as $name => $data) { $data['name'] = $name; $global = array($glob => $data); $this->getInfo('globals', '4.0.0', $global, $source, $ns); } } } // additional search for constants on global namespace $constants = $reflect->getConstants(false, null); $this->getInfo('constants', '4.0.0', $constants, $source, '\\'); // updates current source versions only if element is not excluded $keys = array('namespaces', 'traits', 'interfaces', 'classes', 'functions', 'constants', 'globals', 'tokens'); foreach ($keys as $key) { foreach ($this->{$key} as $ext => $items) { foreach ($items as $name => $data) { if ($data['excluded'] === false) { $this->updateVersion($data['versions'][0], $this->versions[0]); $this->updateVersion($data['versions'][1], $this->versions[1]); } } } } } $this->results[$source] = array('excludes' => $this->excludes, 'includes' => $this->includes, 'versions' => $this->versions, 'extensions' => $this->extensions, 'namespaces' => $this->namespaces, 'traits' => $this->traits, 'interfaces' => $this->interfaces, 'classes' => $this->classes, 'functions' => $this->functions, 'constants' => $this->constants, 'globals' => $this->globals, 'tokens' => $this->tokens, 'conditions' => $conditions); if ($conditions === false) { // search for conditional code $this->results[$source]['conditions'] = $this->getConditions(null, $source); } if (!$cached) { // write results in a cache to improve speed for later uses $cache->setCache($source, $this->results[$source]); } }
/** * Handle console input and produce the appropriate report requested * * @return void * @throws PHP_CompatInfo_Exception If report is not available. */ public static function main() { $input = new Console_CommandLine(array('name' => 'phpcompatinfo', 'description' => 'PHP_CompatInfo (cli) by Laurent Laville.', 'version' => self::getVersion())); // common options to all sub-commands $input->addOption('xmlFile', array('long_name' => '--configuration', 'action' => 'StoreString', 'description' => 'Read configuration from XML file')); $input->addOption('noConfiguration', array('long_name' => '--no-configuration', 'action' => 'StoreTrue', 'description' => 'Ignore default configuration file ' . '(phpcompatinfo.xml)')); $input->addOption('iniSet', array('short_name' => '-d', 'long_name' => '--ini-set', 'action' => 'StoreArray', 'description' => 'Sets a php.ini directive value')); $input->addOption('verbose', array('short_name' => '-v', 'long_name' => '--verbose', 'action' => 'Counter', 'description' => 'Output more verbose information')); // options relatives and common to sub-commands $referenceOption = new Console_CommandLine_Option('reference', array('long_name' => '--reference', 'action' => 'StoreString', 'description' => 'The name of the reference to use', 'choices' => array('PHP5', 'ALL', 'DYN'))); $reportOption = new Console_CommandLine_Option('report', array('long_name' => '--report', 'action' => 'StoreArray', 'description' => 'Type of report', 'choices' => array('full', 'summary', 'source', 'xml', 'token', 'extension', 'namespace', 'trait', 'interface', 'class', 'function', 'constant', 'global', 'condition'))); $helpReferenceOption = new Console_CommandLine_Option('helpReference', array('long_name' => '--help-reference', 'action' => 'List', 'description' => 'List of reference available', 'action_params' => array('list' => array('PHP5', 'ALL', 'DYN')))); $helpReportOption = new Console_CommandLine_Option('helpReport', array('long_name' => '--help-report', 'action' => 'List', 'description' => 'List of report available', 'action_params' => array('list' => array('full', 'summary', 'source', 'xml', 'token', 'extension', 'namespace', 'trait', 'interface', 'class', 'function', 'constant', 'global', 'condition')))); $reportFileOption = new Console_CommandLine_Option('reportFile', array('long_name' => '--report-file', 'action' => 'StoreString', 'description' => 'Write the report to the specified file path')); $excludeIDOption = new Console_CommandLine_Option('excludeID', array('long_name' => '--exclude-pattern', 'action' => 'StoreString', 'description' => 'Exclude components' . ' from list referenced by ID provided')); $recursiveOption = new Console_CommandLine_Option('recursive', array('short_name' => '-R', 'long_name' => '--recursive', 'action' => 'StoreTrue', 'description' => 'Includes the contents of subdirectories')); $fileExtensionsOption = new Console_CommandLine_Option('fileExtensions', array('long_name' => '--file-extensions', 'action' => 'StoreString', 'description' => 'A comma separated list of file extensions to check')); // options relatives to print sub-command $filterVersionOption = new Console_CommandLine_Option('filterVersion', array('long_name' => '--filter-version', 'action' => 'StoreString', 'description' => 'The version to compare with each element found')); $filterOperatorOption = new Console_CommandLine_Option('filterOperator', array('long_name' => '--filter-operator', 'action' => 'StoreString', 'description' => 'The version test relationship', 'choices' => array('lt', 'le', 'gt', 'ge', 'eq', 'ne'))); // argument common to all list sub-commands except to list and list-references $referenceArgument = new Console_CommandLine_Argument('reference', array('description' => '(optional) Limit output only to this reference', 'optional' => true)); // clear-cache sub-command $clearcacheCmd = $input->addCommand('clear-cache', array('description' => 'Clear Parser Cache')); $clearcacheCmd->addArgument('sourceFile', array('description' => 'The source file in cache entries to delete.', 'optional' => true)); // print sub-command $printCmd = $input->addCommand('print', array('description' => 'Print a report of data source parsed.')); $printCmd->addOption($referenceOption); $printCmd->addOption($reportOption); $printCmd->addOption($reportFileOption); $printCmd->addOption($excludeIDOption); $printCmd->addOption($recursiveOption); $printCmd->addOption($fileExtensionsOption); $printCmd->addOption($filterVersionOption); $printCmd->addOption($filterOperatorOption); $printCmd->addOption($helpReferenceOption); $printCmd->addOption($helpReportOption); $printCmd->addArgument('sourcePath', array('description' => 'The data source to scan (file or directory).')); // list-references sub-command $listReferencesCmd = $input->addCommand('list-references', array('description' => 'List all extensions supported.')); $listReferencesCmd->addOption($filterVersionOption); $listReferencesCmd->addOption($filterOperatorOption); $listReferencesCmd->addOption($reportFileOption); $listReferencesCmd->addArgument($referenceArgument); // list sub-command $listCmd = $input->addCommand('list', array('description' => 'List all "elements" referenced in the data base.')); $listCmd->addOption($referenceOption); $listCmd->addOption($filterVersionOption); $listCmd->addOption($filterOperatorOption); $listCmd->addOption($reportFileOption); $listCmd->addOption($helpReferenceOption); $listCmd->addArgument('element', array('description' => 'May be either ' . '"extensions", ' . '"interfaces", "classes", ' . '"functions" or "constants"', 'choices' => array('extensions', 'interfaces', 'classes', 'functions', 'constants'), 'multiple' => true)); // list-extensions sub-command $listExtensionsCmd = $input->addCommand('list-extensions', array('description' => 'List all extensions referenced in the data base.')); $listExtensionsCmd->addOption($referenceOption); $listExtensionsCmd->addOption($filterVersionOption); $listExtensionsCmd->addOption($filterOperatorOption); $listExtensionsCmd->addOption($reportFileOption); $listExtensionsCmd->addOption($helpReferenceOption); $listExtensionsCmd->addArgument($referenceArgument); // list-interfaces sub-command $listInterfacesCmd = $input->addCommand('list-interfaces', array('description' => 'List all interfaces referenced in the data base.')); $listInterfacesCmd->addOption($referenceOption); $listInterfacesCmd->addOption($filterVersionOption); $listInterfacesCmd->addOption($filterOperatorOption); $listInterfacesCmd->addOption($reportFileOption); $listInterfacesCmd->addOption($helpReferenceOption); $listInterfacesCmd->addArgument($referenceArgument); // list-classes sub-command $listClassesCmd = $input->addCommand('list-classes', array('description' => 'List all classes referenced in the data base.')); $listClassesCmd->addOption($referenceOption); $listClassesCmd->addOption($filterVersionOption); $listClassesCmd->addOption($filterOperatorOption); $listClassesCmd->addOption($reportFileOption); $listClassesCmd->addOption($helpReferenceOption); $listClassesCmd->addArgument($referenceArgument); // list-functions sub-command $listFunctionsCmd = $input->addCommand('list-functions', array('description' => 'List all functions referenced in the data base.')); $listFunctionsCmd->addOption($referenceOption); $listFunctionsCmd->addOption($filterVersionOption); $listFunctionsCmd->addOption($filterOperatorOption); $listFunctionsCmd->addOption($reportFileOption); $listFunctionsCmd->addOption($helpReferenceOption); $listFunctionsCmd->addArgument($referenceArgument); // list-constants sub-command $listConstantsCmd = $input->addCommand('list-constants', array('description' => 'List all constants referenced in the data base.')); $listConstantsCmd->addOption($referenceOption); $listConstantsCmd->addOption($filterVersionOption); $listConstantsCmd->addOption($filterOperatorOption); $listConstantsCmd->addOption($reportFileOption); $listConstantsCmd->addOption($helpReferenceOption); $listConstantsCmd->addArgument($referenceArgument); try { $result = $input->parse(); $command = $result->command_name; if (empty($command)) { $input->displayUsage(1); } } catch (Exception $e) { $input->displayError($e->getMessage()); } $warnings = array(); // Loads the default or custom configuration (if available) $options = array('reference' => '', 'verbose' => false, 'listeners' => array()); $reports = array(); $consoleProgress = true; $reportFileAppend = false; if ($result->options['noConfiguration'] !== true) { if (!isset($result->options['xmlFile'])) { // use default configuration $dir = '@cfg_dir@' . DIRECTORY_SEPARATOR . 'PHP_CompatInfo'; if (strpos($dir, '@') === false) { // PEAR installer was used to install the package } else { // manual install $dir = getcwd(); } $filename = $dir . DIRECTORY_SEPARATOR . 'phpcompatinfo.xml'; if (file_exists($filename)) { $config = $filename; } elseif (file_exists($filename . '.dist')) { $config = $filename . '.dist'; } else { $config = false; } } else { $filename = $result->options['xmlFile']; if (file_exists($filename)) { $config = realpath($filename); } else { $config = false; } } if ($config && is_file($config)) { // try to load the configuration file contents $configuration = PHP_CompatInfo_Configuration::getInstance($config); // check if components should be excluded if (isset($result->command->options['excludeID'])) { $patternID = $result->command->options['excludeID']; $excludes = $configuration->getExcludeConfiguration($patternID); if (count($excludes) == 0) { $warnings[] = "Exclude pattern ID '{$patternID}'" . " does not exist, or is empty"; } else { $haystack = array('extension', 'interface', 'trait', 'function', 'constant'); foreach ($excludes as $key => $values) { if (in_array($key, $haystack)) { $options['exclude'][$key . 's'] = $values; } elseif ('class' == $key) { $options['exclude']['classes'] = $values; } else { foreach ($values as $value) { $options['exclude']['files'][] = $value; } } } } } // set main options $phpcompatinfo = $configuration->getMainConfiguration(); if (isset($phpcompatinfo['reference'])) { $options['reference'] = $phpcompatinfo['reference']; } if (isset($phpcompatinfo['report'])) { $reports = $phpcompatinfo['report']; } if (isset($phpcompatinfo['reportFile'])) { $reportFile = $phpcompatinfo['reportFile']; } if (isset($phpcompatinfo['reportFileAppend'])) { $reportFileAppend = $phpcompatinfo['reportFileAppend']; } if (isset($phpcompatinfo['cacheDriver'])) { $options['cacheDriver'] = $phpcompatinfo['cacheDriver']; } else { $options['cacheDriver'] = 'null'; } if (isset($phpcompatinfo['consoleProgress'])) { $consoleProgress = $phpcompatinfo['consoleProgress']; } if (isset($phpcompatinfo['verbose'])) { $options['verbose'] = $phpcompatinfo['verbose']; } if (isset($phpcompatinfo['recursive'])) { $options['recursive'] = $phpcompatinfo['recursive']; } if (isset($phpcompatinfo['fileExtensions'])) { $options['fileExtensions'] = $phpcompatinfo['fileExtensions']; } // sets cache options $options['cacheOptions'] = $configuration->getCacheConfiguration($options['cacheDriver']); // sets extension references limit $extensions = $configuration->getReferenceConfiguration(); if (count($extensions) > 0) { $options['extensions'] = $extensions; } // sets php.ini directives $ini = $configuration->getPHPConfiguration(); foreach ($ini as $name => $value) { ini_set($name, $value); } // sets listeners instances $listeners = $configuration->getListenerConfiguration(); foreach ($listeners as $listener) { if (!class_exists($listener['class'], false) && $listener['file'] !== '') { include_once $listener['file']; } if (class_exists($listener['class'], true)) { if (count($listener['arguments']) == 0) { $listener = new $listener['class'](); } else { $listenerClass = new ReflectionClass($listener['class']); $listener = $listenerClass->newInstanceArgs($listener['arguments']); } if ($listener instanceof PHP_CompatInfo_Listener_Console && $consoleProgress === false) { /* Do not add the console listener if consoleProgress directive is off */ } else { if ($listener instanceof SplObserver) { $options['listeners'][] = $listener; } } } } // sets plugins system $plugins = $configuration->getPluginConfiguration(); foreach ($plugins as $plugin) { if (!class_exists($plugin['class'], false) && $plugin['file'] !== '') { include_once $plugin['file']; } if (class_exists($plugin['class'], true)) { $pluginClass = new ReflectionClass($plugin['class']); $reference = $pluginClass->newInstanceArgs($plugin['args']); if (!$reference instanceof PHP_CompatInfo_Reference_PluginsAbstract) { $warnings[] = "Plugin '" . $plugin['class'] . "' is not valid"; } unset($reference); } } if (count($plugins) > 0) { $options['referencePlugins'] = $plugins; } } elseif (isset($result->options['verbose'])) { $warnings[] = 'File "' . $filename . '" does not exist'; } } if ($consoleProgress === true) { $options['listeners'][] = new PHP_CompatInfo_Listener_Console(); } if (isset($result->command->options['reference'])) { $options['reference'] = $result->command->options['reference']; } if (empty($options['reference']) && !in_array($command, array('list-references', 'clear-cache'))) { $input->displayError('You must supply at least a reference'); } if (isset($result->options['iniSet'])) { foreach ($result->options['iniSet'] as $iniSet) { $ini = explode('=', $iniSet); if (isset($ini[0])) { if (isset($ini[1])) { ini_set($ini[0], $ini[1]); } else { ini_set($ini[0], true); } } } } if (isset($result->command->options['report'])) { $reports = $result->command->options['report']; } if (empty($reports)) { // default report $reports = array('summary'); } if (in_array('full', $reports)) { // when 'full' alias report is specified, ignored all others $reports = array(); $reportFileAppend = true; $fullReport = true; } $reportChilds = $reports; if (isset($result->command->options['reportFile'])) { $reportFile = $result->command->options['reportFile']; } if (isset($reportFile)) { if (is_dir($reportFile) || !file_exists(dirname($reportFile))) { $warnings[] = 'Report file: "' . $reportFile . '" is invalid'; } else { $options['reportFile'] = $reportFile; if ($reportFileAppend === true) { $options['reportFileFlags'] = FILE_APPEND; } else { $options['reportFileFlags'] = 0; } } } if (isset($result->command->options['filterVersion'])) { $options['filterVersion'] = $result->command->options['filterVersion']; } if (isset($result->command->options['filterOperator'])) { $options['filterOperator'] = $result->command->options['filterOperator']; } if ('print' == $command) { if (count($reports) == 0 && !isset($fullReport)) { $input->displayError('You must supply at least a type of report'); } $source = $result->command->args['sourcePath']; $report = 'full'; } elseif ('list' == $command) { $elements = $result->command->args['element']; $source = array_shift($elements); $report = 'reference'; $options['filterReference'] = null; } elseif ('list' == substr($command, 0, 4)) { list(, $source) = explode('-', $command); if ($source == 'references') { $report = 'database'; } else { $report = 'reference'; $elements = array(); } $options['filterReference'] = $result->command->args['reference']; } if (isset($result->command->options['recursive'])) { $options['recursive'] = $result->command->options['recursive']; } if (isset($result->command->options['fileExtensions'])) { $fileExtensions = explode(',', $result->command->options['fileExtensions']); $options['fileExtensions'] = array_map('trim', $fileExtensions); } if (isset($result->options['verbose'])) { $options['verbose'] = $result->options['verbose']; } if ('clear-cache' == $command) { $defaultOptions = PHP_CompatInfo::getDefaultOptions(); $driver = isset($options['cacheDriver']) ? $options['cacheDriver'] : $defaultOptions['cacheDriver']; $source = $result->command->args['sourceFile']; $cache = PHP_CompatInfo_Cache::getInstance($driver, $defaultOptions); $count = $cache->deleteCache($source); printf('%d cache entries cleared%s', $count, PHP_EOL); } else { try { self::factory($report, $source, $options, $warnings, $reportChilds); if ($report == 'reference') { $options['reportFileFlags'] = FILE_APPEND; while (count($elements) > 0) { $source = array_shift($elements); self::factory($report, $source, $options, $warnings, null); } } } catch (PHP_CompatInfo_Exception $e) { print $e->getMessage() . PHP_EOL; exit(1); } } }