Whenever i add an extension to Magento following error occurs:
Fatal error: Call-time pass-by-reference has been removed in
C:\xampp1\htdocs\magento\app\code\community\Bricks\Xmlbrowser\Helper\Data.php
on line 39
I am using Magento 1.8.00-alpha1
public function searchFiles($dir, $str, $filesList = array()) {
$dir = rtrim($dir, '\\\/').DS;
$files = scandir($dir);
foreach ($files as $file) {
if (is_dir($dir.$file)) {
if (($file != '.') && ($file != '..')) {
$this->searchFiles($dir.$file, $str, &$filesList);
}
}
else {
$needToSearch = true;
if ($this->_file_types && is_array($this->_file_types)) {
$needToSearch = in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), $this->_file_types);
}
if ($needToSearch) {
$fileContent = file_get_contents($dir.$file);
if ($needToSearch) {
$fileContent = file_get_contents($dir.$file);
$result = $this->_case_sensitive? strpos($fileContent, $str) : stripos($fileContent, $str);
if ($result !== FALSE) {
$filesList[] = $dir.$file;
}
}
}
}
return $filesList;
}
}
First, change
$this->searchFiles($dir.$file, $str, &$filesList); // This line is 39
to
$this->searchFiles($dir.$file, $str, $filesList); // This line is 39
Second, change
public function searchFiles($dir, $str, $filesList = array()) {
to
public function searchFiles($dir, $str, &$filesList = array()) {
Third, change the 6th line within the method searchFiles()
$this->searchFiles($dir.$file, $str, &$filesList);
to
$this->searchFiles($dir.$file, $str, $filesList);
This should fix the specific fatal error that you're asking about here.
But...
...even if fixed, most probably just the next fatal error will popup.
Like I aready wrote in the comments, my guess is you're running a PHP 5.4 system, but use Magento 3rd party extensions, which are not PHP 5.4 compatible.
If PHP 5.4 is a must on your target system, I'd recommend to contact the authors of the extensions and ask for 5.4 compatible versions of their extensions, or patches to make them 5.4 compatible. You can of course also try to fix them all by yourself, or hire a developer to do it.
If PHP 5.4 is not mandatory, I'd probably check whether downgrading the system to PHP 5.3.latest won't be the better/cheaper/less time consuming option.
Related
I have a script for finding files with a higher revision (based on a file naming convention).
I.E Ultimate Spreadsheet (Rev A).xls gets removed if Ultimate Spreadsheet (Rev B).xls existed. It does the same with version numbers pretty well. I'm looking to add functionality for basically the same thing to happen - but to include files with no "Rev", as some filenames have no "Rev" at all.
So for example I wanted to add functionality for:
Ultimate Powerpoint.ppt to be removed if Ultimate Powerpoint (Rev A).ppt or Ultimate Powerpoint (Rev B).ppt existed. I think I almost have the code to do this already based on it working with the first rule mentioned above, but I didn't write it myself and am pretty naff with PHP so was wondering if someone could point me in the right direction. Many thanks! My current code:
$fileList = trim(shell_exec("ls -a -1"));
$fileArray = explode("\n", $fileList);
shell_exec('mkdir -p Removed');
// Do this magic for every file
foreach ($fileArray as $thisFile)
{
if (!$thisFile) continue;
// Probably already been removed
if (!file_exists($thisFile)) continue;
$niceName = trim(preg_replace('%[\(|\[].*%', '', $thisFile));
preg_quote(pathinfo($thisGame, PATHINFO_EXTENSION), '/') . '$/', '', $thisFile);
// Check for reversions e.g. (Rev 2) or (Rev A)
if (preg_match('%\(Rev (\d|[A-Za-z])\)%', $thisFile, $revNum))
{
if (is_numeric($revNum[1]))
{
$revNum = intval($revNum[1]);
}
else
{
$revNum = ord($revNum[1]);
}
}
$otherVersions = trim(shell_exec("ls -1 \"{$niceName} (\"*\"(Rev \"* 2>/dev/null"));
if ($otherVersions)
{
$otherVersionArray = explode("\n", $otherVersions);
foreach ($otherVersionArray as $otherFile)
{
preg_match('%\(Rev (\d|[A-Za-z])\)%', $otherFile, $thisRev);
if (is_numeric($thisRev[1]))
{
$thisRev = intval($thisRev[1]);
}
else
{
$thisRev = ord($thisRev[1]);
}
if (isset($revNum) && $revNum < $thisRev)
{
// Other version is newer, bin ours
echo "{$thisFile} has an inferior version number/letter [{$revNum} VS. {$thisRev}] - Moved to Removed folder.\n";
shell_exec("mv ".escapeshellarg($thisFile)." Removed/");
continue 2;
}
}
}
}
I'm trying to access the mime.types file from a model in codeIgniter.
function get_mime_type($filename, $mimePath = '../../assets'){
$fileext = substr(strrchr($filename, '.'), 1);
if (empty($fileext)) return (false);
$regex = "/^([\w\+\-\.\/]+)\s+(\w+\s)*($fileext\s)/i";
$lines = file("$mimePath/mime.types");
foreach($lines as $line){
if (substr($line, 0, 1) == '#') continue; // skip comments
$line = rtrim($line) . " ";
if (!preg_match($regex, $line, $matches)) continue; // no match to the extension
return ($matches[1]);
}
return (false); // no match at all
}
I've already tried the following when I call it:
$this->get_mime_type($filename, $mimePath = '../../assets');
$this->get_mime_type($filename, $mimePath = '/zenoir/assets');
$this->get_mime_type($filename, $mimePath = 'http://localhost:8080/zenoir/assets/mime.types');
But no luck. The mime.types file is in the assets folder and the model is inzenoir/application/models
The error was:
A PHP Error was encountered
Severity: Warning
Message: file(../../assets/mime.types) [function.file]: failed to open stream: No such file or directory
Filename: models/files.php
Line Number: 46
Why are you getting into so much trouble for the mime types, use mime_content_type() instead.
echo mime_content_type($mimePath.$filename);
And the problem with your code is the path issue. Use BASEPATH constant and traverse from there instead. You will omit all these sort of path issue.
This question already has answers here:
count lines in a PHP project [closed]
(7 answers)
Closed 9 years ago.
I need to count the number of lines of code within my application (in PHP, not command line), and since the snippets on the web didn't help too much, I've decided to ask here.
Thanks for any reply!
EDIT
Actually, I would need the whole snippet for scanning and counting lines within a given folder. I'm using this method in CakePHP, so I'd appreciate seamless integration.
To do it over a directory, I'd use an iterator.
function countLines($path, $extensions = array('php')) {
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path)
);
$files = array();
foreach ($it as $file) {
if ($file->isDir() || $file->isDot()) {
continue;
}
$parts = explode('.', $file->getFilename());
$extension = end($parts);
if (in_array($extension, $extensions)) {
$files[$file->getPathname()] = count(file($file->getPathname()));
}
}
return $files;
}
That will return an array with each file as the key and the number of lines as the value. Then, if you want only a total, just do array_sum(countLines($path));...
You can use the file function to read the file and then count:
$c = count(file('filename.php'));
$fp = "file.php";
$lines = file($fp);
echo count($lines);
Using ircmaxell's code, I made a simple class out of it, it works great for me now
<?php
class Line_Counter
{
private $filepath;
private $files = array();
public function __construct($filepath)
{
$this->filepath = $filepath;
}
public function countLines($extensions = array('php'))
{
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->filepath));
foreach ($it as $file)
{
// if ($file->isDir() || $file->isDot())
if ($file->isDir() )
{
continue;
}
$parts = explode('.', $file->getFilename());
$extension = end($parts);
if (in_array($extension, $extensions))
{
$files[$file->getPathname()] = count(file($file->getPathname()));
}
}
return $files;
}
public function showLines()
{
echo '<pre>';
print_r($this->countLines());
echo '</pre>';
}
public function totalLines()
{
return array_sum($this->countLines());
}
}
// Get all files with line count for each into an array
$loc = new Line_Counter('E:\Server\htdocs\myframework');
$loc->showLines();
echo '<br><br> Total Lines of code: ';
echo $loc->totalLines();
?>
PHP Classes has a nice class for counting lines for php files in a directory:
http://www.phpclasses.org/package/1091-PHP-Calculates-the-total-lines-of-code-in-a-directory.html
You can specify the file types you want to check at the top of the class.
https://github.com/sebastianbergmann/phploc
a little dirty, but you can also use system / exec / passthru wc -l *
How can I find any unused functions in a PHP project?
Are there features or APIs built into PHP that will allow me to analyse my codebase - for example Reflection, token_get_all()?
Are these APIs feature rich enough for me not to have to rely on a third party tool to perform this type of analysis?
You can try Sebastian Bergmann's Dead Code Detector:
phpdcd is a Dead Code Detector (DCD) for PHP code. It scans a PHP project for all declared functions and methods and reports those as being "dead code" that are not called at least once.
Source: https://github.com/sebastianbergmann/phpdcd
Note that it's a static code analyzer, so it might give false positives for methods that only called dynamically, e.g. it cannot detect $foo = 'fn'; $foo();
You can install it via PEAR:
pear install phpunit/phpdcd-beta
After that you can use with the following options:
Usage: phpdcd [switches] <directory|file> ...
--recursive Report code as dead if it is only called by dead code.
--exclude <dir> Exclude <dir> from code analysis.
--suffixes <suffix> A comma-separated list of file suffixes to check.
--help Prints this usage information.
--version Prints the version and exits.
--verbose Print progress bar.
More tools:
https://phpqa.io/
Note: as per the repository notice, this project is no longer maintained and its repository is only kept for archival purposes. So your mileage may vary.
Thanks Greg and Dave for the feedback. Wasn't quite what I was looking for, but I decided to put a bit of time into researching it and came up with this quick and dirty solution:
<?php
$functions = array();
$path = "/path/to/my/php/project";
define_dir($path, $functions);
reference_dir($path, $functions);
echo
"<table>" .
"<tr>" .
"<th>Name</th>" .
"<th>Defined</th>" .
"<th>Referenced</th>" .
"</tr>";
foreach ($functions as $name => $value) {
echo
"<tr>" .
"<td>" . htmlentities($name) . "</td>" .
"<td>" . (isset($value[0]) ? count($value[0]) : "-") . "</td>" .
"<td>" . (isset($value[1]) ? count($value[1]) : "-") . "</td>" .
"</tr>";
}
echo "</table>";
function define_dir($path, &$functions) {
if ($dir = opendir($path)) {
while (($file = readdir($dir)) !== false) {
if (substr($file, 0, 1) == ".") continue;
if (is_dir($path . "/" . $file)) {
define_dir($path . "/" . $file, $functions);
} else {
if (substr($file, - 4, 4) != ".php") continue;
define_file($path . "/" . $file, $functions);
}
}
}
}
function define_file($path, &$functions) {
$tokens = token_get_all(file_get_contents($path));
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
if (is_array($token)) {
if ($token[0] != T_FUNCTION) continue;
$i++;
$token = $tokens[$i];
if ($token[0] != T_WHITESPACE) die("T_WHITESPACE");
$i++;
$token = $tokens[$i];
if ($token[0] != T_STRING) die("T_STRING");
$functions[$token[1]][0][] = array($path, $token[2]);
}
}
}
function reference_dir($path, &$functions) {
if ($dir = opendir($path)) {
while (($file = readdir($dir)) !== false) {
if (substr($file, 0, 1) == ".") continue;
if (is_dir($path . "/" . $file)) {
reference_dir($path . "/" . $file, $functions);
} else {
if (substr($file, - 4, 4) != ".php") continue;
reference_file($path . "/" . $file, $functions);
}
}
}
}
function reference_file($path, &$functions) {
$tokens = token_get_all(file_get_contents($path));
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
if (is_array($token)) {
if ($token[0] != T_STRING) continue;
if ($tokens[$i + 1] != "(") continue;
$functions[$token[1]][1][] = array($path, $token[2]);
}
}
}
?>
I'll probably spend some more time on it so I can quickly find the files and line numbers of the function definitions and references; this information is being gathered, just not displayed.
This bit of bash scripting might help:
grep -rhio ^function\ .*\( .|awk -F'[( ]' '{print "echo -n " $2 " && grep -rin " $2 " .|grep -v function|wc -l"}'|bash|grep 0
This basically recursively greps the current directory for function definitions, passes the hits to awk, which forms a command to do the following:
print the function name
recursively grep for it again
piping that output to grep -v to filter out function definitions so as to retain calls to the function
pipes this output to wc -l which prints the line count
This command is then sent for execution to bash and the output is grepped for 0, which would indicate 0 calls to the function.
Note that this will not solve the problem calebbrown cites above, so there might be some false positives in the output.
USAGE: find_unused_functions.php <root_directory>
NOTE: This is a ‘quick-n-dirty’ approach to the problem. This script only performs a lexical pass over the files, and does not respect situations where different modules define identically named functions or methods. If you use an IDE for your PHP development, it may offer a more comprehensive solution.
Requires PHP 5
To save you a copy and paste, a direct download, and any new versions, are available here.
#!/usr/bin/php -f
<?php
// ============================================================================
//
// find_unused_functions.php
//
// Find unused functions in a set of PHP files.
// version 1.3
//
// ============================================================================
//
// Copyright (c) 2011, Andrey Butov. All Rights Reserved.
// This script is provided as is, without warranty of any kind.
//
// http://www.andreybutov.com
//
// ============================================================================
// This may take a bit of memory...
ini_set('memory_limit', '2048M');
if ( !isset($argv[1]) )
{
usage();
}
$root_dir = $argv[1];
if ( !is_dir($root_dir) || !is_readable($root_dir) )
{
echo "ERROR: '$root_dir' is not a readable directory.\n";
usage();
}
$files = php_files($root_dir);
$tokenized = array();
if ( count($files) == 0 )
{
echo "No PHP files found.\n";
exit;
}
$defined_functions = array();
foreach ( $files as $file )
{
$tokens = tokenize($file);
if ( $tokens )
{
// We retain the tokenized versions of each file,
// because we'll be using the tokens later to search
// for function 'uses', and we don't want to
// re-tokenize the same files again.
$tokenized[$file] = $tokens;
for ( $i = 0 ; $i < count($tokens) ; ++$i )
{
$current_token = $tokens[$i];
$next_token = safe_arr($tokens, $i + 2, false);
if ( is_array($current_token) && $next_token && is_array($next_token) )
{
if ( safe_arr($current_token, 0) == T_FUNCTION )
{
// Find the 'function' token, then try to grab the
// token that is the name of the function being defined.
//
// For every defined function, retain the file and line
// location where that function is defined. Since different
// modules can define a functions with the same name,
// we retain multiple definition locations for each function name.
$function_name = safe_arr($next_token, 1, false);
$line = safe_arr($next_token, 2, false);
if ( $function_name && $line )
{
$function_name = trim($function_name);
if ( $function_name != "" )
{
$defined_functions[$function_name][] = array('file' => $file, 'line' => $line);
}
}
}
}
}
}
}
// We now have a collection of defined functions and
// their definition locations. Go through the tokens again,
// and find 'uses' of the function names.
foreach ( $tokenized as $file => $tokens )
{
foreach ( $tokens as $token )
{
if ( is_array($token) && safe_arr($token, 0) == T_STRING )
{
$function_name = safe_arr($token, 1, false);
$function_line = safe_arr($token, 2, false);;
if ( $function_name && $function_line )
{
$locations_of_defined_function = safe_arr($defined_functions, $function_name, false);
if ( $locations_of_defined_function )
{
$found_function_definition = false;
foreach ( $locations_of_defined_function as $location_of_defined_function )
{
$function_defined_in_file = $location_of_defined_function['file'];
$function_defined_on_line = $location_of_defined_function['line'];
if ( $function_defined_in_file == $file &&
$function_defined_on_line == $function_line )
{
$found_function_definition = true;
break;
}
}
if ( !$found_function_definition )
{
// We found usage of the function name in a context
// that is not the definition of that function.
// Consider the function as 'used'.
unset($defined_functions[$function_name]);
}
}
}
}
}
}
print_report($defined_functions);
exit;
// ============================================================================
function php_files($path)
{
// Get a listing of all the .php files contained within the $path
// directory and its subdirectories.
$matches = array();
$folders = array(rtrim($path, DIRECTORY_SEPARATOR));
while( $folder = array_shift($folders) )
{
$matches = array_merge($matches, glob($folder.DIRECTORY_SEPARATOR."*.php", 0));
$moreFolders = glob($folder.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
$folders = array_merge($folders, $moreFolders);
}
return $matches;
}
// ============================================================================
function safe_arr($arr, $i, $default = "")
{
return isset($arr[$i]) ? $arr[$i] : $default;
}
// ============================================================================
function tokenize($file)
{
$file_contents = file_get_contents($file);
if ( !$file_contents )
{
return false;
}
$tokens = token_get_all($file_contents);
return ($tokens && count($tokens) > 0) ? $tokens : false;
}
// ============================================================================
function usage()
{
global $argv;
$file = (isset($argv[0])) ? basename($argv[0]) : "find_unused_functions.php";
die("USAGE: $file <root_directory>\n\n");
}
// ============================================================================
function print_report($unused_functions)
{
if ( count($unused_functions) == 0 )
{
echo "No unused functions found.\n";
}
$count = 0;
foreach ( $unused_functions as $function => $locations )
{
foreach ( $locations as $location )
{
echo "'$function' in {$location['file']} on line {$location['line']}\n";
$count++;
}
}
echo "=======================================\n";
echo "Found $count unused function" . (($count == 1) ? '' : 's') . ".\n\n";
}
// ============================================================================
/* EOF */
2020 Update
I have used the other methods outlined above, even the 2019 update answer here is outdated.
Tomáš Votruba's answer led me to find Phan as the ECS route has now been deprecated. Symplify have removed the dead public method checker.
Phan is a static analyzer for PHP
We can utilise Phan to search for dead code. Here are the steps to take using composer to install. These steps are also found on the git repo for phan. These instructions assume you're at the root of your project.
Step 1 - Install Phan w/ composer
composer require phan/phan
Step 2 - Install php-ast
PHP-AST is a requirement for Phan
As I'm using WSL, I've been able to use PECL to install, however, other install methods for php-ast can be found in a git repo
pecl install ast
Step 3 - Locate and edit php.ini to use php-ast
Locate current php.ini
php -i | grep 'php.ini'
Now take that file location and nano (or whichever of your choice to edit this doc). Locate the area of all extensions and ADD the following line:
extension=ast.so
Step 4 - create a config file for Phan
Steps on config file can be found in Phan's documentation on how to create a config file
You'll want to use their sample one as it's a good starting point. Edit the following arrays to add your own paths on both
directory_list & exclude_analysis_directory_list.
Please note that exclude_analysis_directory_list will still be parsed but not validated eg. adding Wordpress directory here would mean, false positives for called wordpress functions in your theme would not appear as it found the function in wordpress but at the same time it'll not validate functions in wordpress' folder.
Mine looked like this
......
'directory_list' => [
'public_html'
],
......
'exclude_analysis_directory_list' => [
'vendor/',
'public_html/app/plugins',
'public_html/app/mu-plugins',
'public_html/admin'
],
......
Step 5 - Run Phan with dead code detection
Now that we've installed phan and ast, configured the folders we wish to parse, it's time to run Phan. We'll be passing an argument to phan --dead-code-detection which is self explanatory.
./vendor/bin/phan --dead-code-detection
This output will need verifying with a fine tooth comb but it's certainly the best place to start
The output will look like this in console
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
Please feel free to add to this answer or correct my mistakes :)
If I remember correctly you can use phpCallGraph to do that. It'll generate a nice graph (image) for you with all the methods involved. If a method is not connected to any other, that's a good sign that the method is orphaned.
Here's an example: classGallerySystem.png
The method getKeywordSetOfCategories() is orphaned.
Just by the way, you don't have to take an image -- phpCallGraph can also generate a text file, or a PHP array, etc..
Because PHP functions/methods can be dynamically invoked, there is no programmatic way to know with certainty if a function will never be called.
The only certain way is through manual analysis.
2019+ Update
I got inspied by Andrey's answer and turned this into a coding standard sniff.
The detection is very simple yet powerful:
finds all methods public function someMethod()
then find all method calls ${anything}->someMethod()
and simply reports those public functions that were never called
It helped me to remove over 20+ methods I would have to maintain and test.
3 Steps to Find them
Install ECS:
composer require symplify/easy-coding-standard --dev
Set up ecs.yaml config:
# ecs.yaml
services:
Symplify\CodingStandard\Sniffs\DeadCode\UnusedPublicMethodSniff: ~
Run the command:
vendor/bin/ecs check src
See reported methods and remove those you don't fine useful 👍
You can read more about it here: Remove Dead Public Methods from Your Code
phpxref will identify where functions are called from which would facilitate the analysis - but there's still a certain amount of manual effort involved.
afaik there is no way. To know which functions "are belonging to whom" you would need to execute the system (runtime late binding function lookup).
But Refactoring tools are based on static code analysis. I really like dynamic typed languages, but in my view they are difficult to scale. The lack of safe refactorings in large codebases and dynamic typed languages is a major drawback for maintainability and handling software evolution.
How can I find any unused functions in a PHP project?
Are there features or APIs built into PHP that will allow me to analyse my codebase - for example Reflection, token_get_all()?
Are these APIs feature rich enough for me not to have to rely on a third party tool to perform this type of analysis?
You can try Sebastian Bergmann's Dead Code Detector:
phpdcd is a Dead Code Detector (DCD) for PHP code. It scans a PHP project for all declared functions and methods and reports those as being "dead code" that are not called at least once.
Source: https://github.com/sebastianbergmann/phpdcd
Note that it's a static code analyzer, so it might give false positives for methods that only called dynamically, e.g. it cannot detect $foo = 'fn'; $foo();
You can install it via PEAR:
pear install phpunit/phpdcd-beta
After that you can use with the following options:
Usage: phpdcd [switches] <directory|file> ...
--recursive Report code as dead if it is only called by dead code.
--exclude <dir> Exclude <dir> from code analysis.
--suffixes <suffix> A comma-separated list of file suffixes to check.
--help Prints this usage information.
--version Prints the version and exits.
--verbose Print progress bar.
More tools:
https://phpqa.io/
Note: as per the repository notice, this project is no longer maintained and its repository is only kept for archival purposes. So your mileage may vary.
Thanks Greg and Dave for the feedback. Wasn't quite what I was looking for, but I decided to put a bit of time into researching it and came up with this quick and dirty solution:
<?php
$functions = array();
$path = "/path/to/my/php/project";
define_dir($path, $functions);
reference_dir($path, $functions);
echo
"<table>" .
"<tr>" .
"<th>Name</th>" .
"<th>Defined</th>" .
"<th>Referenced</th>" .
"</tr>";
foreach ($functions as $name => $value) {
echo
"<tr>" .
"<td>" . htmlentities($name) . "</td>" .
"<td>" . (isset($value[0]) ? count($value[0]) : "-") . "</td>" .
"<td>" . (isset($value[1]) ? count($value[1]) : "-") . "</td>" .
"</tr>";
}
echo "</table>";
function define_dir($path, &$functions) {
if ($dir = opendir($path)) {
while (($file = readdir($dir)) !== false) {
if (substr($file, 0, 1) == ".") continue;
if (is_dir($path . "/" . $file)) {
define_dir($path . "/" . $file, $functions);
} else {
if (substr($file, - 4, 4) != ".php") continue;
define_file($path . "/" . $file, $functions);
}
}
}
}
function define_file($path, &$functions) {
$tokens = token_get_all(file_get_contents($path));
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
if (is_array($token)) {
if ($token[0] != T_FUNCTION) continue;
$i++;
$token = $tokens[$i];
if ($token[0] != T_WHITESPACE) die("T_WHITESPACE");
$i++;
$token = $tokens[$i];
if ($token[0] != T_STRING) die("T_STRING");
$functions[$token[1]][0][] = array($path, $token[2]);
}
}
}
function reference_dir($path, &$functions) {
if ($dir = opendir($path)) {
while (($file = readdir($dir)) !== false) {
if (substr($file, 0, 1) == ".") continue;
if (is_dir($path . "/" . $file)) {
reference_dir($path . "/" . $file, $functions);
} else {
if (substr($file, - 4, 4) != ".php") continue;
reference_file($path . "/" . $file, $functions);
}
}
}
}
function reference_file($path, &$functions) {
$tokens = token_get_all(file_get_contents($path));
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
if (is_array($token)) {
if ($token[0] != T_STRING) continue;
if ($tokens[$i + 1] != "(") continue;
$functions[$token[1]][1][] = array($path, $token[2]);
}
}
}
?>
I'll probably spend some more time on it so I can quickly find the files and line numbers of the function definitions and references; this information is being gathered, just not displayed.
This bit of bash scripting might help:
grep -rhio ^function\ .*\( .|awk -F'[( ]' '{print "echo -n " $2 " && grep -rin " $2 " .|grep -v function|wc -l"}'|bash|grep 0
This basically recursively greps the current directory for function definitions, passes the hits to awk, which forms a command to do the following:
print the function name
recursively grep for it again
piping that output to grep -v to filter out function definitions so as to retain calls to the function
pipes this output to wc -l which prints the line count
This command is then sent for execution to bash and the output is grepped for 0, which would indicate 0 calls to the function.
Note that this will not solve the problem calebbrown cites above, so there might be some false positives in the output.
USAGE: find_unused_functions.php <root_directory>
NOTE: This is a ‘quick-n-dirty’ approach to the problem. This script only performs a lexical pass over the files, and does not respect situations where different modules define identically named functions or methods. If you use an IDE for your PHP development, it may offer a more comprehensive solution.
Requires PHP 5
To save you a copy and paste, a direct download, and any new versions, are available here.
#!/usr/bin/php -f
<?php
// ============================================================================
//
// find_unused_functions.php
//
// Find unused functions in a set of PHP files.
// version 1.3
//
// ============================================================================
//
// Copyright (c) 2011, Andrey Butov. All Rights Reserved.
// This script is provided as is, without warranty of any kind.
//
// http://www.andreybutov.com
//
// ============================================================================
// This may take a bit of memory...
ini_set('memory_limit', '2048M');
if ( !isset($argv[1]) )
{
usage();
}
$root_dir = $argv[1];
if ( !is_dir($root_dir) || !is_readable($root_dir) )
{
echo "ERROR: '$root_dir' is not a readable directory.\n";
usage();
}
$files = php_files($root_dir);
$tokenized = array();
if ( count($files) == 0 )
{
echo "No PHP files found.\n";
exit;
}
$defined_functions = array();
foreach ( $files as $file )
{
$tokens = tokenize($file);
if ( $tokens )
{
// We retain the tokenized versions of each file,
// because we'll be using the tokens later to search
// for function 'uses', and we don't want to
// re-tokenize the same files again.
$tokenized[$file] = $tokens;
for ( $i = 0 ; $i < count($tokens) ; ++$i )
{
$current_token = $tokens[$i];
$next_token = safe_arr($tokens, $i + 2, false);
if ( is_array($current_token) && $next_token && is_array($next_token) )
{
if ( safe_arr($current_token, 0) == T_FUNCTION )
{
// Find the 'function' token, then try to grab the
// token that is the name of the function being defined.
//
// For every defined function, retain the file and line
// location where that function is defined. Since different
// modules can define a functions with the same name,
// we retain multiple definition locations for each function name.
$function_name = safe_arr($next_token, 1, false);
$line = safe_arr($next_token, 2, false);
if ( $function_name && $line )
{
$function_name = trim($function_name);
if ( $function_name != "" )
{
$defined_functions[$function_name][] = array('file' => $file, 'line' => $line);
}
}
}
}
}
}
}
// We now have a collection of defined functions and
// their definition locations. Go through the tokens again,
// and find 'uses' of the function names.
foreach ( $tokenized as $file => $tokens )
{
foreach ( $tokens as $token )
{
if ( is_array($token) && safe_arr($token, 0) == T_STRING )
{
$function_name = safe_arr($token, 1, false);
$function_line = safe_arr($token, 2, false);;
if ( $function_name && $function_line )
{
$locations_of_defined_function = safe_arr($defined_functions, $function_name, false);
if ( $locations_of_defined_function )
{
$found_function_definition = false;
foreach ( $locations_of_defined_function as $location_of_defined_function )
{
$function_defined_in_file = $location_of_defined_function['file'];
$function_defined_on_line = $location_of_defined_function['line'];
if ( $function_defined_in_file == $file &&
$function_defined_on_line == $function_line )
{
$found_function_definition = true;
break;
}
}
if ( !$found_function_definition )
{
// We found usage of the function name in a context
// that is not the definition of that function.
// Consider the function as 'used'.
unset($defined_functions[$function_name]);
}
}
}
}
}
}
print_report($defined_functions);
exit;
// ============================================================================
function php_files($path)
{
// Get a listing of all the .php files contained within the $path
// directory and its subdirectories.
$matches = array();
$folders = array(rtrim($path, DIRECTORY_SEPARATOR));
while( $folder = array_shift($folders) )
{
$matches = array_merge($matches, glob($folder.DIRECTORY_SEPARATOR."*.php", 0));
$moreFolders = glob($folder.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
$folders = array_merge($folders, $moreFolders);
}
return $matches;
}
// ============================================================================
function safe_arr($arr, $i, $default = "")
{
return isset($arr[$i]) ? $arr[$i] : $default;
}
// ============================================================================
function tokenize($file)
{
$file_contents = file_get_contents($file);
if ( !$file_contents )
{
return false;
}
$tokens = token_get_all($file_contents);
return ($tokens && count($tokens) > 0) ? $tokens : false;
}
// ============================================================================
function usage()
{
global $argv;
$file = (isset($argv[0])) ? basename($argv[0]) : "find_unused_functions.php";
die("USAGE: $file <root_directory>\n\n");
}
// ============================================================================
function print_report($unused_functions)
{
if ( count($unused_functions) == 0 )
{
echo "No unused functions found.\n";
}
$count = 0;
foreach ( $unused_functions as $function => $locations )
{
foreach ( $locations as $location )
{
echo "'$function' in {$location['file']} on line {$location['line']}\n";
$count++;
}
}
echo "=======================================\n";
echo "Found $count unused function" . (($count == 1) ? '' : 's') . ".\n\n";
}
// ============================================================================
/* EOF */
2020 Update
I have used the other methods outlined above, even the 2019 update answer here is outdated.
Tomáš Votruba's answer led me to find Phan as the ECS route has now been deprecated. Symplify have removed the dead public method checker.
Phan is a static analyzer for PHP
We can utilise Phan to search for dead code. Here are the steps to take using composer to install. These steps are also found on the git repo for phan. These instructions assume you're at the root of your project.
Step 1 - Install Phan w/ composer
composer require phan/phan
Step 2 - Install php-ast
PHP-AST is a requirement for Phan
As I'm using WSL, I've been able to use PECL to install, however, other install methods for php-ast can be found in a git repo
pecl install ast
Step 3 - Locate and edit php.ini to use php-ast
Locate current php.ini
php -i | grep 'php.ini'
Now take that file location and nano (or whichever of your choice to edit this doc). Locate the area of all extensions and ADD the following line:
extension=ast.so
Step 4 - create a config file for Phan
Steps on config file can be found in Phan's documentation on how to create a config file
You'll want to use their sample one as it's a good starting point. Edit the following arrays to add your own paths on both
directory_list & exclude_analysis_directory_list.
Please note that exclude_analysis_directory_list will still be parsed but not validated eg. adding Wordpress directory here would mean, false positives for called wordpress functions in your theme would not appear as it found the function in wordpress but at the same time it'll not validate functions in wordpress' folder.
Mine looked like this
......
'directory_list' => [
'public_html'
],
......
'exclude_analysis_directory_list' => [
'vendor/',
'public_html/app/plugins',
'public_html/app/mu-plugins',
'public_html/admin'
],
......
Step 5 - Run Phan with dead code detection
Now that we've installed phan and ast, configured the folders we wish to parse, it's time to run Phan. We'll be passing an argument to phan --dead-code-detection which is self explanatory.
./vendor/bin/phan --dead-code-detection
This output will need verifying with a fine tooth comb but it's certainly the best place to start
The output will look like this in console
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
the/path/to/php/file.php:324 PhanUnreferencedPublicMethod Possibly zero references to public method\the\path\to\function::the_funciton()
Please feel free to add to this answer or correct my mistakes :)
If I remember correctly you can use phpCallGraph to do that. It'll generate a nice graph (image) for you with all the methods involved. If a method is not connected to any other, that's a good sign that the method is orphaned.
Here's an example: classGallerySystem.png
The method getKeywordSetOfCategories() is orphaned.
Just by the way, you don't have to take an image -- phpCallGraph can also generate a text file, or a PHP array, etc..
Because PHP functions/methods can be dynamically invoked, there is no programmatic way to know with certainty if a function will never be called.
The only certain way is through manual analysis.
2019+ Update
I got inspied by Andrey's answer and turned this into a coding standard sniff.
The detection is very simple yet powerful:
finds all methods public function someMethod()
then find all method calls ${anything}->someMethod()
and simply reports those public functions that were never called
It helped me to remove over 20+ methods I would have to maintain and test.
3 Steps to Find them
Install ECS:
composer require symplify/easy-coding-standard --dev
Set up ecs.yaml config:
# ecs.yaml
services:
Symplify\CodingStandard\Sniffs\DeadCode\UnusedPublicMethodSniff: ~
Run the command:
vendor/bin/ecs check src
See reported methods and remove those you don't fine useful 👍
You can read more about it here: Remove Dead Public Methods from Your Code
phpxref will identify where functions are called from which would facilitate the analysis - but there's still a certain amount of manual effort involved.
afaik there is no way. To know which functions "are belonging to whom" you would need to execute the system (runtime late binding function lookup).
But Refactoring tools are based on static code analysis. I really like dynamic typed languages, but in my view they are difficult to scale. The lack of safe refactorings in large codebases and dynamic typed languages is a major drawback for maintainability and handling software evolution.