Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Roave/FunctionFQNReplacer

Repository files navigation

Function FQN Replacer

This utility provides a way to replace relative references of functions in function calls with absolute references.

Rationale

As explained in this twitter convo and this article, PHP is resolving relative function references at call time.

This is relatively normal, as PHP, by design, cannot make decisions on which file defines which functions, because all opcode caching is local to single scripts, and not to a project.

In addition to that, PHP can only apply optimizations about internal function calls when those calls happen either in the global namespace, or are fully qualified name (FQN) calls.

Here's an example of the opcodes generated for an call_user_func() call without and with FQN reference:

<?php

namespace Foo {
    call_user_func('foo');
}

Opcodes:

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   4     0  E >   INIT_NS_FCALL_BY_NAME                                    
         1        SEND_VAL_EX                                              'foo'
         2        DO_FCALL                                      0          
   5     3      > RETURN                                                   1
<?php

namespace Foo {
    // this is a FQN reference:
    \call_user_func('foo');
}
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   4     0  E >   INIT_USER_CALL                                0          'call_user_func', 'foo'
         1        DO_FCALL                                      0          
   5     2      > RETURN                                                   1

As you can see, INIT_NS_FCALL_BY_NAME is gone. This is one of the many optimizations applied by PHP 7 and newer versions (see zend_compile.c for more examples).

Benchmark

All of the above sounds like a silly and pointless micro-optimization, but it makes a huge difference when it comes to commonly and widespread libraries.

In order to state the point more clearly, some benchmarks are provided with this package.

Simply run php -n ./vendor/bin/phpbench run --revs=1000 --iterations=10 --warmup=2 --report=aggregate from within this project:

$ php -n ./vendor/bin/phpbench run --revs=1000 --iterations=10 --warmup=2 --report=aggregate
PhpBench 0.13.0. Running benchmarks.
Using configuration file: FunctionFQNReplacer/phpbench.json

\RoaveBench\FunctionFQNReplacer\AbsoluteFunctionReferenceBench

    benchCallUserFuncWithRelativeReferenceI2 P0 	[μ Mo]/r: 2.603 2.586 (μs) [μSD μRSD]/r: 0.034μs 1.31%
    benchCallUserFuncWithAbsoluteReferenceI2 P0 	[μ Mo]/r: 1.767 1.635 (μs) [    benchCallUserFuncWithAbsoluteReferenceR3 I2 P0 	[μ Mo]/r: 1.793 1.799 (μs) [μSD μRSD]/r: 0.009μs 0.53%

2 subjects, 6 iterations, 200 revs, 0 rejects
(best [mean mode] worst) = 1.780 [2.198 2.192] 1.800 (μs)
⅀T: 13.190μs μSD/r 0.022μs μRSD/r: 0.916%
suite: 133a2c6cc9c7a295d7b89ff84b2cfff4f39d8935, date: 2016-12-22, stime: 23:10:51
+----------------------------------------+---------+---------+---------+---------+---------+--------+---------+
| subject                                | best    | mean    | mode    | worst   | stdev   | rstdev | diff    |
+----------------------------------------+---------+---------+---------+---------+---------+--------+---------+
| benchCallUserFuncWithRelativeReference | 2.642μs | 2.729μs | 2.694μs | 2.839μs | 0.062μs | 2.28%  | +68.78% |
| benchCallUserFuncWithAbsoluteReference | 1.577μs | 1.617μs | 1.610μs | 1.688μs | 0.029μs | 1.77%  | 0.00%   |
+----------------------------------------+---------+---------+---------+---------+---------+--------+---------+

As you can see, call_user_func() vs \call_user_func() is a sensible difference.

Feel free to add benchmarks to the benchmark/ directory.

Installation

This project is not meant to be run as a dependency: please install it as standalone.

composer create-project roave/function-fqn-replacer

Usage

Please beware that this project uses the internal code generator of nikic/php-parser. This means that it will break your coding style when recreating the sources of your PHP files. This is a known and unresolved issue.

./function-fqn-replacer path/to/project/files path/to/existing/functions another/path/to/existing/functions

The first argument is the path to the directory where you want the FQN references to be replaced.

Additional parameters are the paths where function definitions can be found. The tool needs to know these in order to avoid replacing functions that may not be internal.

Alternatives

This tool was built in a rush, but is quite well tested and based on solid background foundations. Still, it will modify whitespace alignment in your source files due to technical limitations of the current PHP AST parser.

If you don't like these consequences, consider using nilportugues/php-backslasher instead, which has been around for more time, and uses a lower level implementation of token replacement which preserves whitespace alignment