Find out where a function comes from? - php

From time to time, I'm handed bloated, undocumented, buggy PHP code to fix. I have several tools I use to fix such code as quickly as possible.
One thing I'm not sure how to do that occasionally causes me grief is finding where a function is located (in which file it is defined). Note that I don't want to know where a function is called from.
For example I'd like to be able to do this:
//file1.php
function foo(){
echo 'bar';
}
.
//file2.php
where_is_function('foo');//error
include('file1.php');
echo where_is_function('foo');//outputs'file1.php'

Get yourself a good IDE such as netbeans or eclipse. They have functionality to find function declarations, and also find the usages of those functions (under refactoring).
Personally I use netbeans. I simply have to ctrl-click on a function name & netbeans will find where the function is defined, and automatically open the file for me.

grep or grep-like tools. I prefer ack, which makes the output much easier to read and ignores version-control files by default. I highly prefer these tools to constraining myself to code in a bloated, click-hogging IDE. Finding things this way has always worked better than things like Eclipse for me anyway.
To use grep, you would cd to the base directory and then recursively grep the contents of the directory for the function name, possibly including the keywords to define a function. Like this:
cd project/
grep -Rn "def wallace(" .
Ack is like:
cd project/
ack "def wallace("

Try this:
function getFunctionLocation($fname) {
$includeFilesString = implode(" ", get_included_files());
return system("grep 'function $fname' $includeFilesString -l");
}
But if it's only for development purposes, which it should be, then just run
grep -r "function functionName" *
from the base directory

Related

Validate the syntax of PHP files more efficiently

Validating the syntax of a bunch of PHP files is SLOW
We use php -l file.php to validate the syntax of many php files as part of a continuous integration setup. We actually do something like: `find . -name "*.php" | xargs --max-args=1 php -l" because the php executable only takes one argument.
This is horrendously slow and mostly because it involves firing up a brand new parser / interpreter (not to mention process) for each PHP file in order to verify it's syntax and we have thousands.
Is there a faster way?
What about adding a time in the search eg
`find . -mtime -7 -name "*.php" | xargs --max-args=1 php -l
to the find command to only validate the files that have been modified on the last week?
I am presuming most of your code base does not change every few days?
Updated
You might also want to try the -newer flag
`find . -newer /path/to/file -name "*.php" | xargs --max-args=1 php -l
it finds all files newer than the one given, very handy, especially if your version control changes a certain system file every time you checkout alternatively use:
touch -t 201303121000 /path/to/file
to create a dummy file for use with -newer
I've given up on php -l entirely for the same reason, though in my case (and perhaps in yours) it doesn't matter.
Since I'm using PHPUnit for my unit tests I don't need to lint the files being tested. If the file wouldn't pass the linter it won't pass any tests either (even one which simply includes the file).
If you haven't covered 100% of your files with PHPUnit, you may be able to fake the effect of the linter with something like:
class FakeLinterTest extends PHPUnit_Framework_TestCase {
public function testLintAllTheFiles() {
foreach ($this->listAllPHPFiles() as $file) {
include_once($file);
}
}
private function listAllPHPFiles() {
// Traverse your entire source tree.
}
}
That code is entirely untested. Also, if you have a big project you may need to play games with memory limits and/or break up the "lint" into chunks to stop it murdering your CI system.

Makefile: how to use (or change) variable value in PHP config file?

I'm trying to create a Makefile for my HTML5/PHP project. I have a PHP config file like this:
<?php
...
$mode = "development"; # production / development / debug
...
?>
I would like to force that value to "production" from my Makefile.
I have a shell script which works (at least to read the value):
echo `perl -ne 'print if s/.*\\\$mode\s*=\s*"(.*)".*/$1/g' config.php`
but it doesn't in the Makefile:
MODE = $(shell echo `perl -ne 'print if s/.*\\\$mode\s*=\s*"(.*)".*/\$1/g' config.php`)
( $(MODE) is always empty... )
I know I could use an external script, but I would prefer not to.
Probably my approach is not the most correct, I'd love to hear about different solutions to such Makefile / PHP issues...
PHP is probably the most straightforward and robust language to parse PHP config files:
MODE = $(shell make -s get-php-var-mode)
get-php-var-%:
php -r 'require("config.php"); print_r("$$$*\n");'
print-MODE:
#echo ${MODE}
That's for reading into Make.
You could also keep the single settings file in a more standard format that both languages can easily parse, like JSON, XML, etc.
I'd recommend at the very least to have the settings in only one place.
As far as altering the settings file programmatically, I've seen basic templating (e.g., ###REPLACEME###) used with success. You keep a parent template file. And different build targets generate different versions of the template file, with appropriate variable values replaced. Works for managing different config files across dev and prod servers, as well as managing different config files across developers.
What you will typically need a preprocessor for PHP files. When you set some variable in your Makefile, say PRODUCTION, your preprocessor will place different code so that you will get production code.
I haven't used but I came across C Compatible Preprocessor for PHP. You may have a try.
Example
An example code using CCPP would be:
<?php
#ifdef DEVELOPMENT
$mode = "development"; # production / development / debug
#else
$mode = "production";
?>
I figured out the following things. First of all, the bare (shell) command can be simplified:
perl -ne 'print if s/.*\$mode\s*=\s*"(.*)".*/$1/g' config.php
Inside the Makefile the only escape problem I could spot so far was with the $ sign - make tries to process variables here which does not work. The solution I found was to double these $ signs. Makefile example:
test := $(shell echo `perl -ne 'print if s/.*$$mode\s*=\s*"(.*)".*/$$1/g' config.php`)
all:
echo $(test)
Usage/Output:
$ make
echo development
development

Is there a PHP syntax checker for Notepad++?

Is there a PHP syntax checker plugin for Notepad++?
Please don't answer "Use another editor instead"
Try NppExec plugin for Notepad++. Using it create a command to be something like this:
cmd.exe /K c:\your\path\to\php.exe -l "YOUR_FULL_FILE_NAME"
Instead of YOUR_FULL_FILE_NAME you should use appropriate Notepadd++ macro -- I think it is $(FULL_CURRENT_PATH) but double check with NppExec manual (installs together with plugin).
P.S.
But any IDE will be better for sure (I'm using PhpStorm). If IDE is too heavy for your PC then look for php-oriented editors, like Blumentals RapidPHP etc (it's lighter than full IDE but may have all really important features).
As LazyOne said above, you can use NppExec which you can install using the plugin manager (Plugins>Plugin Manager>Show Plugin Manager) You'll also need to have PHP installed. Lastly, the command I use to do PHP syntax checking with NppExec is
"C:\Program Files (x86)\PHP\php.exe" -l $(FULL_CURRENT_PATH)
I recommend you find a true IDE (not a glorified text editor). I've used Notepad++ for years but it can't do much beyond syntax highlighting.
I personally use PHPStorm (but it's not free, it is very good though :D ). You could also use NetBeans or Eclipse.
Adding to #LazyOne's answer:
I don't like NetBeans, it's too strict, has a tough time finding includes, and it's slow. I dig N++ for its speed and simplicity. I have php installed on my PC really just to run validation. If you're using N++ (or any other text editor) you can use the following Powershell script to batch check all of the files you've downloaded and are working on. Just fire up Powershell ISE, enter the correct path to check and PHP.exe path for your environment and the results get output to the ISE console.
cls
$pathToCheck = "C:\Users\BigDaddy\AppData\Local\Temp\fz3temp-1"
$phpExePath = "C:\PHP\php.exe"
Get-ChildItem $pathToCheck -Filter "*.php" | foreach {
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $phpExePath
$pinfo.Arguments = "-l", $_.FullName
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$output = $p.StandardOutput.ReadToEnd()
$output += $p.StandardError.ReadToEnd()
$output
}
I hope someone else finds this as useful as I have.
Cheers!
I use Komodo Edit 7 (free version) which has a built-in php syntax checker. I dunno how robust it is, but it works fine for me. I’m not a pro web designer, but I like it better then Eclipse and Bluefish. Komodo is smaller than Eclipse and more stable than Bluefish (in my Win XP environment).
PHP can syntax check your file using the -l lint option. Install PHP (if you haven't already) on your computer and use the Run function in Notepad++ and run a command like this:
cmd.exe /S /K ""C:\Program (portable)\php-8.0.12-Win32-vs16-x64\php.exe" -l "$(FULL_CURRENT_PATH)""
Change the path to your install location. After running it via Run you are able to save it, give it a name and assign a custom keyboard shortcut.
Breakdown of the command:
cmd.exe /S /K open a new Command shell which will stay open after executing, /S for being able to wrap the whole command to execute in cmd.exe in quotes
"C:\Program (portable)\php-8.0.12-Win32-vs16-x64\php.exe" launch php.exe
-l option of php.exe to use their lint service rather than executing
"$(FULL_CURRENT_PATH)" Notepad++ specific which gives the full path to the current open document

Vim: undefined variables are unmarked

Lately I've been making a couple of mistakes when refactoring in Vim, the result was undefined and unused variables. Any decent IDE (like NetBeans) would mark them as such, but I've yet to come across a Vim plugin that does the same.
Can anyone help me out? I'm mainly programming in PHP.
There should be a solution with the Syntastic plugin, on which you would need to put a PHP static code analyzer like PHPLint.
However I never spent some time to test this !
Other PHP programs can be found on this SO answer.
You can run Zend's PHP code analyzer from within VIM. I currently do this. The catch is that Zend Code Analyzer is no longer packaged as a separate binary when installing Zend Studio. I'm not sure which OS you are running. I'm running on OS X. If you don't already have the binary, use steps 1 & 2 on this site to get it - http://michalf.me/blog:zend-code-analyzer-in-textmate. You may have to adjust for your OS.
After getting the binary add the following to your .vimrc and replace the /usr/local/... with the path to your ZendCodeAnalyzer.
if !exists("autocommands_loaded")
let autocommands_loaded = 1
"PHP Make
autocmd BufRead *.inc,*.php set makeprg=/usr/local/bin/ZendCodeAnalyzer\ %
autocmd BufRead *.inc,*.php set errorformat=%f(line\ %l):\ %m
endif
map <F7> :silent lmake<cr>:lwindow <cr>:redraw!<cr>
Now when you enter F7 it will run make which is set to run the ZendCodeAnalyzer. It will put the results into a location list - :help location. You can scroll through the location list and hit enter on a line and it will take you to that line in your file. If it doesn't find anything, then it won't open anything.
Well, this might not be what you are looking for, but if you must have Vim keybindings (I know I need them), then jVi brings this to NetBeans. I don't know if this is a viable option for you, but maybe this will help.
When renaming vars in a whole file type in vi cmd line:
:%s/\$oldName/\$newName/
When renaming the between line 14 and 21 (e.g inside a function) type
:14,21s/\$oldName/\$newName/
When renaming vars recursively in a directory type in vi cmd line:
:!find DIRECTORY_PATH -name "*.php" | xargs sed -ni 's/\$oldName/\$newName/'
Make a backup of the folder before to avoid headaches. ;)
I'm not sure how intelligent this plugin is but it seems to do what you want: https://github.com/vim-scripts/php_localvarcheck.vim

Passing parameters to PHPUnit

I'm starting to write PHPUnit tests and I'd like the tests to be run from developers machines as well as from our servers. Developers machines are set up differently than the servers and even differently from each other.
To run in these different places it seems to be the person that runs the test is going to have to indicate where it's being run. The test can then look up the proper config of the machine it's running on.
I'm imagining something like:
phpunit.bat -X johns_laptop unittest.php
or on the alpha server:
phpunit -X alpha unittest.php
In the test I would be able to get the value if the 'X' (or whatever it is) parameter and know, for example, what the path to the app root is for this machine.
It doesn't look like the command line allows for that - or have I missed something?
You can use PHPUnit's --bootstrap switch for this.
--bootstrap <file> A "bootstrap" PHP file that is run before the tests.
Then, make a bootstrap.php file that contains variables:
$port = 4445;
In your tests, you can grab those values:
global $port;
$this->setPort($port);
Then run:
phpunit --bootstrap boot.php MyTest.php
One way would be for you to inspect $argv and $argc. Something like:
<?php
require_once 'PHPUnit/Framework/TestCase.php';
class EnvironmentTest extends PHPUnit_Framework_TestCase {
public function testHasParam() {
global $argv, $argc;
$this->assertGreaterThan(2, $argc, 'No environment name passed');
$environment = $argv[2];
}
}
Then you can call your phpunittest like this:
phpunit EnvironmentTest.php my-computer
An elegant way to pass variables to both bootstrap files as well as to test files is by using environment variables:
export MY_ENV_VAR="some value"
phpunit all
Then, in your PHP files, you can access it like this:
getenv('MY_ENV_VAR')
Source: http://blog.lysender.com/2010/10/phpunit-passing-environment-variable-to-your-application/
As Jasir already mentioned, a one line solution would be to set environment variable before phpunit call.
On Linux:
X=alpha phpunit unittest.php
On Windows probably:
set X=johns_laptop && phpunit.bat unittest.php
And inside your script use
getenv('X')
to read the value
I don't think answers above solve my same problem.
The accepted answer is not perfect. In this way, custom options should always be put to the end of the parameters list, and there is no indicator to tell that they are custom options. If the number of custom options which I need is not fixed, I should code a lot to parse custom options with regular expressions or something like that.
The environment variables solution is good, but not natural. Looks weird.
VAR1=aaa VAR2=bbb VAR3=ccc ./phpunit-custom-option CustomOptionTest.php
The shell script plus setUp() solution share the same weakness with the accepted one. May be you should code a lot to parse the file and handle unpredictable numbers of custom options.
I don't think the bootstrap script is the correct solution. It could be used to handle dirty works automatically, with doing same things every time but not dealing with change parts good.
I don't like all the above answers.
And I have no good idea myself too. But maybe what I've done could give you inspiration. I've forked the phpunit project on GitHub, and modified code a little, and made it to support the custom option feature.
Modified version of phpunit, could accept custom options like this:
./phpuint-custom-option --custom var1=value1 --custom var2=value2 CustomOptionTest.php
And in the test, you can visit the custom options by accessing the super global variables $_SERVER
<?php
class CustomOptionTest extends PHPUnit_Framework_TestCase {
public function testCustomOption() {
$this->assertEquals('value1', $_SERVER['var1']);
$this->assertEquals('value2', $_SERVER['var2']);
}
}
and you can find my code here, and download the modified version here (by click the "view the full file" link on the page).
FYI. this article is the similar solution.
All the solutions here are valid for the question, but there is yet another way that might be simpler for some situations.
Phing will take arguments passed in the form -Dargument=value
So using
phing -Dtest=MyTest.class.php
You can then use phing conditionals to handle these arguments:
<if>
<isset property="test" />
<then>
<property name="testFile" value="${test}" />
</then>
<else>
<property name="testFile" value="AllTests.php" />
</else>
</if>
<exec command="phpunit --bootstrap myTestFile/bootstrap.php- myTestFolder/${testFile}"
passthru="true" returnproperty="phpunitreturn" />
I struggled with this exact issue, and came up with a kind of hacky-yet-convenient solution: I write parameters to a file on disk and retrieve them in the setUp() method:
public function setUp() {
$this->setBrowser('firefox');
$this->base_url = file_get_contents("selenium_host");
$this->setBrowserUrl($this->base_url);
}
Rather than calling phpunit or paratest directly, I have a shell script to launch the tests. This one invokes paratest and lets me specify the number of processes as well as the host I'd like the tests to run against.
run_all_tests.sh
if [ $1 ]
then
threads=$1
else
threads=5
fi
if [ $2 ]
then
echo $2 > selenium_host
else
echo 'http://defaulthost.com' > selenium_host
fi
vendor/bin/paratest -p$threads -f --colors TestSuite.php
Then, to run with 7 threads against http://adifferenthost.com:
./run_all_tests.sh 7 'http://adifferenthost.com'
Passing arguments on the command line would make sense if you want to vary the test parameters per test run. Running host-specific tests on different machines is not the best justification for this solution.
For that, the PHPUnit configuration file may prove to be more suitable. It gives you control over host- and even request-specific variables including manipulating php.ini settings as well as defining constants, global variables, $_ENV, $_SERVER, and even $_GET, $_POST, etc. This is all done under the <php> node of the configuration file, see Setting PHP INI settings, Constants and Global Variables
Symfony2 uses this approach and provides both a phpunit.xml.dist (default config) and a phpunit.xml with your unit tests. The latter is gitignored to allow you to customize it for each machine without affecting the repo. You would then run your tests with:
phpunit -c /path/to/phpunit.xml
No need of environment variables nor special scripts. You can use -d option to set php.ini values.
<?php // tests/IniTest.php
use PHPUnit\Framework\TestCase;
class IniTest extends TestCase
{
public $server;
public function setUp(): void
{
$this->server = ini_get('my.custom.server') ?: '127.0.0.1'; // fallback
}
public function testDummy()
{
$this->assertIsString($this->server);
}
}
Execute the code like this:
vendor/bin/phpunit -d my.custom.server=192.168.1.15 tests/IniTest.php
Setting custom php.ini values is valid. For example you can configure a PDO connection alias with pdo.dsn.*. https://www.php.net/manual/en/pdo.construct.php
If you would like to run tests on remote machine, use ssh then run it. On locale machine you only have to cd to your root dir, then run phpunit.
user#local:/path/to/your/project$ phpunit
user#remote:/var/www/project$ phpunit
Edit: You are talking about a machine dependent configuration. (What kind of conf btw?) My solution is to put these config under the same, not versioncontrolled place, then read/parse it runtime, in the needed set up methds for example.

Categories