How to deprecate a function in PHP? - php

At the team with which I work, we have an old codebase using PHP's ibase_* functions all over the code to communicate with database. We created a wrapper to it that would do something else beside just calling the original function and I did a mass search-replace in the entire code to make sure that wrapper is used instead.
Now, how do we prevent usage of ibase_* functions in the future?
Preferably, I'd like to still have them available, but make it throw a NOTICE or WARNING when it is used.
A solution in pure PHP (not needing to compile a custom version of PHP) is preferred.

trigger_error()
function my_deprecated_function() {
trigger_error("Deprecated function called.", E_USER_NOTICE);
// do stuff.
}

Generally speaking you can flag a method as deprecated to give your users warnings about code that will not work in future versions. I think the best way is to use trigger_error along with some phpdoc.
/**
* #deprecated
*
* #return $this
*/
public function oldMethod()
{
trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
return $this;
}
The #deprecated phpdoc is important because many IDEs like PHPStorm recognise it and strike the method name if you try to use it, so you notice that is deprecated before actually running your code.
It will look more or less like this:
Beside the phpdoc you can make sure the user gets a warning by triggering the right error at runtime. Just make sure you use the right constant (i.e. E_USER_DEPRECATED).
E_DEPRECATED is instead used internally by PHP thus you should not be using it. More information here.

If I understand correct, you want to trigger an error when a built-in PHP function is used? In that case, take a look at the Override Function function.

I haven't checked it by myself, but found this in my bookmarks: http://wiki.php.net/rfc/e-user-deprecated-warning
Edit: Okay this doesn't work yet - so instead of E_USER_DEPRECATED just use something like E_USER_NOTICE:
<?php
class Foo
{
public function __construct()
{
trigger_error('Use Bar instead', E_USER_NOTICE);
}
}
$foo = new Foo()
This will end up with this:
Notice: Use Bar instead in /home/unexist/projects/ingame/svn/foo.php on line 6

If your functions are part of a class, then you could use trigger_error in the constructor to warn of the deprecation.
Alternatively, if the functions are in a single file, then triggering a deprecation warning at the top of the file would show the error whenever the file is included elsewhere.
Finally, you could throw the error on the first line of any of the deprecated functions.

Instead of raising a runtime warning on usage, you could consider writing a script, that can scan your code base for the use of this function, then generate a report of offending code. Once in a while, run it through.
If you use a version control system, you could set the script as a commit-hook. I would probably recommend a post-hook, that simply sends an email, when a script, containing deprecated functions, is checked in, but if you really want to enforce it, you could have a pre-hook completely prevent anyone from checking it in.

Related

Is using the short notation for directly called function safe in PHP7?

Since PHP7 it seems to be possible to directly call anonymous functions like this:
(function () { echo 'Hello!'; })();
I found this by accident in some open source code base. I tried to find any hint on this in the PHP documentation (including changelogs and RFCs). There seems to be none, it seems fully undocumented.
Is it safe to call functions this way or should I use call_user_func(); like in the old days? Is this documented somewhere?
I know of these questions and answers:
IIFE (Immediately Invoked Function Expression) in PHP?
https://stackoverflow.com/a/35044603/1708223
They just say it's supposed to work and how it works. I know this. My question is not about the "how"! It's about whether this is actually an official PHP feature or if it'S merely working "by accident" (because it seems undocumented).
The reason you won't find this documented as a feature in the main manual is because it isn't one, it's just a consequence of other features:
function () { echo 'Hello!'; } creates a Closure object
putting () after a Closure object executes that closure
That's not quite the same as accidental, but I can see why you might think that, because it didn't work in PHP 5.
The reason it changed is that PHP's parser was overhauled in PHP 7.0 to use more consistent rules when parsing variables, as well as introducing an extra stage called an Abstract Syntax Tree. This changed the meaning of some code, but allowed for other cases that logically should have been possible, but were previously hard to implement.
Allowing code that was previously a syntax error is not generally considered a compatibility break, so not every piece of syntax enabled by this change made it into migration guides.
However, removing this functionality - even if not documented - would be a compatibility break, so at the very least you will get some notice that it's going to change, and it would be in a "major version" (9.0, 10.0, etc). Since it's a useful piece of syntax, there's no reason to suppose it will ever stop working, unless there's some even more useful feature that conflicts with it for some reason.
Just to put some thoughts together: (function () { echo 'Hello!'; }) defines a class instance of type Closure. Putting the closure into a variable helps to understand that:
$x = (function () { echo 'Hello!'; });
echo gettype($x);
This prints object, since PHP 5.3. The result of $x(); hasn't changed since PHP 5.3 either. But what has changed is the unified handling of variables - this enables that you don't have to define a variable explicitly, but call the closure directly as if you had put it in a variable first

How to deal with "method not found in class" warning for magically implemented methods?

I am sitting on a large codebase that contains several classes that expose functionality through magically implemented methods (using __call and __callStatic). For example:
class Foo {
public function __call($name, $parameters) {
echo "You called $name().\n";
}
}
$f = new Foo;
$f->test(); // runs fine, but PhpStorm flags as a warning
The problem is that PhpStorm thinks that the test() method does not exist, so it gives a warning at the call site. This is a little annoying, as of course the code will run and behave as expected.
I have already tuned down the severity by checking the "downgrade severity if __magic methods are present in class" option, but I would prefer to either:
completely disable this functionality for specific classes only, or
work with the IDE rather than against it -- provide it with the information I already have so our views agree
Is any of the above possible? If so, how?
Additional bonus question: consider the case where method calls are being chained.
$f = new Foo;
$f->test()->chain()->moreChain(); // potentially runs fine
Assuming that the magic call to $f->test() returns something appropriate the subsequent (possibly, but not necessarily, also magic) calls will work fine. However, since there is no way that I know of to tell the IDE what test() returns it flags the rest of the call chain as full of missing methods too. And to make matters worse, the "downgrade severity" setting does not apply to these warnings since the IDE does not know what class these intermediate objects are supposed to be.
Is there a solution that can also cover this case?
Update
Even though documenting the magic methods with #method annotations seems to work, I have to assume that there are currently several problems with this approach because it only took me a little work to come upon these related bugs:
Type hinting for the method arguments does not work correctly with primitives
Annotations work for one call, but not for chained calls
I do hope they fix them in a reasonable time frame.
Well, you can go to the preference menu, under Inspections, go to Undefined -> Undefined Method and check Downgrade severity if __magic methods are present.
That would make the flag less severe, (instead of Warning, as Info), which would still give you a green light on your document check.
There's nothing else I'm aware of aside from having #property or #method PHPDoc notations on the target class for every method that's likely to be used.
Rather than globally turning off inspections by downgrading the severity of the inspection, you can add a comment to the single line to ignore just that particular inspection.
/** #noinspection PhpUndefinedMethodInspection */
Assertion::NullOrAssertionDoesNotExist();
Building on what Madara said, I found it wouldn't downgrade down far enough for my liking no matter what severity I set it to, so I made a new Severity for undefined method that has no attributes whatsoever, and turned off the downgrade checkbox (see image below)
If I hover over my magic methods now, it still brings up the popup message, but otherwise it doesn't distract me. One step better than just turning off inspection, because at least this way an actual undefined method can still be detected by hovering over the name
You can use a dynamic variable :
$test = 'test';
$chaine = $f->$test(); // no phpStorm flag, & the code works

How can I check a php file for undefined functions?

Calling php -l checks the syntax of a php file.
Is there a way to check for undefined functions? I don't need it to work with functions defined at runtime nor with dynamic calls.
Calling the file obviously won't work: I need to check the whole file, not only the branch that gets executed. See the follwing example.
myfile.php:
function imhere () {}
main.php:
require_once 'myfile.php';
if (true) {
imhere();
}
else {
imnhothere(); // I need to get a warning about this
}
Checking for undefined functions is impossible in a dynamic language. Consider that undefined functions can manifest in lots of ways:
undefined();
array_filter('undefined', $array);
$prefix = 'un'; $f = $prefix.'defined'; $f();
This is in addition to the fact that there may be functions that are used and defined conditionally (through includes or otherwise). In the worst case, consider this scenario:
if(time() & 1) {
function foo() {}
}
foo();
Does the above program call an undefined function?
Check the syntax only need to scan the current content of the php file, but check for function defined or not is something different because functions could be defined in the required file.
The simplest way is just to execute the file, and undefined function will give you a PHP Fatal error: Call to undefined function .....
Or you may want this.
Following comment tennis I thought I would wrap up some thoughts into an answer.
It seems like what you might need is some higher-level testing (either integration or web testing). With these you can test complete modules or your whole application and how they hang together. This would help you isolate calls to undefined functions as well as a host of other things by running tests against whole pages or groups of classes with the relevant includes statements. You can PHPunit for integration testing (as well as for unit-) although I'm not sure that would gain you that much at this point.
Better would be to investigate web-testing. There are a number of tools you could use (this is by no means intended to be a complete list):
Selenium which I believe can hook into PHPUnit
SimpleTest, not sure how well maintained this is but I have used it in the past for simple (!) tests
behat, this implements BDD using Gherkin but I understand it can be used to do web-testing (behat with Goutte

it is possible to extend a php function?

My question is if it's possible to extend a declared function.
I want to extend mysql_function to add mysql query that insert into a table some logs : 'query' - the parameter of mysql_query, date,page...etc
My question is if it's possible to extend a declared function.
No.
You can extend a class method and call parent::methodname() to run the previous code (which is almost what you ask for), but for normal functions, there is no way to do this.
There are some esoteric PHP extensions that allow overriding functions, but I assume that's not what you need and their use is rarely practical.
What you probably want to do is create a new function, and call the existing function in it.
No, you cannot do that. Either enable the MySql Query Logs or wrap the code doing the queries into a Logging Decorator or use an abstraction like Zend_Db that can take a Profiler or use a transparent logging plugin for mysqlnd
You need to write a function that will take your query, log the sql first, runs your query, then return the results.
E.G
<?php
function mysql_query_log($sql)
{
mysql_query('insert into .... values ...');
$r = mysql_query($sql);
$results;
//do the normal thing you do with mysql here
return $results;
}
This is not extending a function though, you can only extend a class
It's not possible.
You should have created your own API (or use an existing one) to access the DB so when you need logging you can simply enhance your own API function. It also comes very handy if you need some custom error handling function. Refactor the code.
Well.. PHP says this: http://php.net/manual/en/function.override-function.php
from http://php.net/manual/en/function.rename-function.php
bool rename_function ( string $original_name , string $new_name )
Renames a orig_name to new_name in the global function table. Useful
for temporarily overriding built-in functions.
I believe that if you rename the original to original_mysql_query, then add your replacement function which does your logging and then calls original_mysql_query etc, that you will achieve your goal, assuming that you have the way to inject the rename on every page that will call MySQL_query. Most large sites have common code that is included at the top of every page that could do that for you.
There is also a built in php function called override_function (mentioned by ChrisH). It is not fully documented in the php man page but the user comments below the doc give you the information that you need to use it if you prefer it to the rename_function function. There was a discussion about being limited to one override if you needed to call the original function from the replacement. Using the rename_function instead of the override function eliminates that potential restriction.

Tracking down use of functions marked for deprecation

Following this thread: How to handle functions deprecation in library? I want to find a way to track all calls to the deprecated function so I can make sure I get them all replaced before the function is removed. Given the following PHP methods
/*
#deprecated - just use getBar()
*/
function getFoo(){
return getBar();
}
function getBar(){
return "bar";
}
I came up with the following method of doing so and I'm looking for feedback.
function getFoo(){
try{
throw new Exception("Deprecated function used");
} catch(Exception $e){
//Log the Exception with stack trace
....
// return value as normal
return getBar();
}
}
For PHPs internal deprecated functions, just add E_STRICT to error_reporting.
For userland functions to raise Notice or Warning about deprecated functions, I'd suggest the developer who took the time to add the #deprecated annotation also triggers an E_USER_DEPRECATED warning, e.g.
function getFoo(){
trigger_error(__FUNCTION__ . 'is deprecated', E_USER_DEPRECATED );
return getBar();
}
I am not aware if any of the available QA tools can automatically detect if code contains deprecated method calls. Those are your best bet though.
You shouldn't need to be concerned about removing deprecated methods or functions if you are using TDD with 100% code coverage. Your automated tests will simply fail and you will know where to look.
Relying on the deprecated function being actually called is dangerous - you would have 100% code coverage to make sure you don't miss anything. It's all right for slowly finding all calls to deprecated functions and replace them one by one, but not good enough for a complete transition.
I think File > Search in Files
in your IDE is your best bet, as there are no good refactoring tools around for PHP that I know of.
Afterthought: Maybe PhpXRef is the solution.

Categories