If your file implement code condition that is optional and don't break main goal,
such as, for example :
if function_exists
then I do something, else I do something else.
Solution is very easy: You have to specify what function required should be considered as optional.
Suppose we have to detect which PHP version we need to run this chunk of script
named "errorHandler.php". With standard behavior, PCI returns
PHP 4.3.0 (because debug_backtrace
came with version 4.3.0).
So, if we ignore function debug_backtrace
to find out
the minimum version, we will get the real and true result.
<?php
// ...
if (function_exists('debug_backtrace')) {
$backtrace = debug_backtrace();
} else {
$backtrace = false;
}
// ...
?>
We will use another very simple detection script. Have a look on options array given as second parameter (here is the magic).
<?php
require_once 'PHP/CompatInfo.php';
$source = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'errorHandler.php';
$options = array('ignore_functions' => array('debug_backtrace'));
$info = new PHP_CompatInfo();
$info->parseFile($source, $options);
// you may also use unified method: $info->parseData($source, $options);
?>
And displayed results are :
array ( 'ignored_files' => array ( ), 'ignored_functions' => array ( 0 => 'debug_backtrace', ), 'ignored_extensions' => array ( ), 'ignored_constants' => array ( ), 'max_version' => '', 'version' => '4.0.0', 'classes' => array ( ), 'extensions' => array ( ), 'constants' => array ( 0 => 'FALSE', ), 'tokens' => array ( ), 'cond_code' => array ( 0 => 1, ), )
that means chunk of script named "errorHandler.php" need PHP 4.0.0, have condition code (function condition : cond_code = 1), and php debug_backtrace function was excluded from scope.
Since version 1.7.0, you may catch this situation (more easily), and exclude from scope all functions that are conditionned by a function_exists. See example that follow.
Other alternative is to use ignore_functions_match option.
<?php
require_once 'PHP/CompatInfo.php';
$source = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'errorHandler.php';
$options = array('ignore_functions_match' => array('function_exists', array('/.*/')));
$info = new PHP_CompatInfo();
$info->parseFile($source, $options);
// you may also use unified method: $info->parseData($source, $options);;
?>
The string function_exists as first parameter, tell to ignore function(s) that match only conditionnal code with php function_exists().
The other possibility is string preg_match, that give more freedom, and catch function that match the pattern (without condition).
While array as second parameter, gave a list of pattern (function name) that must be catch and ignored.
Parsing a full directory, recursively or not, is no more difficult than detect PHP version of a single file.
This new example is based on auto detection of HTML_CSS 1.5.1 distribution. As we will see, basic detection is not accurate as it should be. But with an option we can get the real result (PHP minimum = 4.3.0).
First begin to download the archive from http://pear.php.net/package/HTML_CSS/download/1.5.1 and extract the full contents to a temporary directory (in our example its '/tmp')
We will focus on two important files:
CSS.php
and CSS/Error.php
.
So we will indicate to PCI to ignore
examples/
, and
tests/
directories.
Here is the detection script:
<?php
require_once 'PHP/CompatInfo.php';
$source = '/tmp/HTML_CSS-1.5.1';
$options = array('ignore_dirs' => array('examples', 'tests'));
$info = new PHP_CompatInfo();
$info->parseDir($source, $options);
// you may also use unified method: $info->parseData($source, $options);
?>
And displayed results are :
array ( 'ignored_files' => array ( 0 => '/tmp/HTML_CSS-1.5.1/ChangeLog', 1 => '/tmp/HTML_CSS-1.5.1/package.xml', 2 => '/tmp/HTML_CSS-1.5.1/tests/AllTests.php', 3 => '/tmp/HTML_CSS-1.5.1/tests/HTML_CSS_TestSuite_Bugs.php', 4 => '/tmp/HTML_CSS-1.5.1/tests/HTML_CSS_TestSuite_Standard.php', 5 => '/tmp/HTML_CSS-1.5.1/tests/stylesheet.css', 6 => '/tmp/HTML_CSS-1.5.1/examples/CSS_Advanced.php', 7 => '/tmp/HTML_CSS-1.5.1/examples/CSS_DisplayOnline.php', 8 => '/tmp/HTML_CSS-1.5.1/examples/css_errorstack_custom.php', 9 => '/tmp/HTML_CSS-1.5.1/examples/css_errorstack_logger.php', 10 => '/tmp/HTML_CSS-1.5.1/examples/css_error_custom.php', 11 => '/tmp/HTML_CSS-1.5.1/examples/css_error_ignore.php', 12 => '/tmp/HTML_CSS-1.5.1/examples/css_error_logger.php', 13 => '/tmp/HTML_CSS-1.5.1/examples/CSS_grepStyles.php', 14 => '/tmp/HTML_CSS-1.5.1/examples/CSS_InHeader.php', 15 => '/tmp/HTML_CSS-1.5.1/examples/CSS_Inline.php', 16 => '/tmp/HTML_CSS-1.5.1/examples/CSS_Logger.php', 17 => '/tmp/HTML_CSS-1.5.1/examples/CSS_parseData.php', 18 => '/tmp/HTML_CSS-1.5.1/examples/CSS_req12194_atrule_api.php', 19 => '/tmp/HTML_CSS-1.5.1/examples/CSS_req12194_atrule_parser.php', 20 => '/tmp/HTML_CSS-1.5.1/examples/CSS_Stylesheet.php', 21 => '/tmp/HTML_CSS-1.5.1/examples/CSS_validate.php', ), 'ignored_functions' => array ( ), 'ignored_extensions' => array ( ), 'ignored_constants' => array ( ), 'max_version' => '', 'version' => '5.0.0', 'classes' => array ( 0 => 'Services_W3C_CSSValidator', ), 'extensions' => array ( 0 => 'date', 1 => 'pcre', ), 'constants' => array ( 0 => 'E_USER_ERROR', 1 => 'E_USER_NOTICE', 2 => 'E_USER_WARNING', 3 => 'FALSE', 4 => 'NULL', 5 => 'PHP_OS', 6 => 'PREG_SET_ORDER', 7 => 'PREG_SPLIT_DELIM_CAPTURE', 8 => 'TRUE', ), 'tokens' => array ( ), 'cond_code' => array ( 0 => 1, ), '/tmp/HTML_CSS-1.5.1/CSS.php' => array ( 'ignored_functions' => array ( ), 'ignored_extensions' => array ( ), 'ignored_constants' => array ( ), 'max_version' => '', 'version' => '5.0.0', 'classes' => array ( 0 => 'Services_W3C_CSSValidator', ), 'extensions' => array ( 0 => 'date', 1 => 'pcre', ), 'constants' => array ( 0 => 'FALSE', 1 => 'NULL', 2 => 'PHP_OS', 3 => 'PREG_SET_ORDER', 4 => 'PREG_SPLIT_DELIM_CAPTURE', 5 => 'TRUE', ), 'tokens' => array ( ), 'cond_code' => array ( 0 => 1, ), ), '/tmp/HTML_CSS-1.5.1/CSS/Error.php' => array ( 'ignored_functions' => array ( ), 'ignored_extensions' => array ( ), 'ignored_constants' => array ( ), 'max_version' => '', 'version' => '4.3.0', 'classes' => array ( ), 'extensions' => array ( 0 => 'date', ), 'constants' => array ( 0 => 'E_USER_ERROR', 1 => 'E_USER_NOTICE', 2 => 'E_USER_WARNING', 3 => 'FALSE', 4 => 'NULL', 5 => 'TRUE', ), 'tokens' => array ( ), 'cond_code' => array ( 0 => 0, ), ), )
As we can read, PHP 5.0.0 is required to run the package.
Yes, but its the wrong result. HTML_CSS 1.5.1 require only PHP 4.3.0 to run.
If you have
cond_code
offset with a value different than zero, you are almost sure that the version given is wrong.
So why we get such result ?
Package PEAR::HTML_CSS 1.5.1 as many application/extension use conditional code to emulate function that are unavailable for previous PHP versions. Its means that HTML_CSS use the php function function_exists() to implement such alternative.
To illustrate our purpose, we can find in source code (CSS.php) :
<?php
// ...
if (function_exists('file_put_contents')) {
file_put_contents($filename, $this->toString());
} else {
$file = fopen($filename, 'wb');
fwrite($file, $this->toString());
fclose($file);
}
// ...
?>
PHP function file_put_contents() came with version 5.0.0; That is the reason of wrong parsing result. But we can catch such conditional code.
Let's see now how to set the good accuracy with conditional code analysis.