Tuesday, April 6. 2010A Primer for PHP 5.3's New Language FeaturesFor the past month, I've been immersed in PHP 5.3 as I and my team have started work on Zend Framework 2.0. PHP 5.3 offers a slew of new language features, many of which were developed to assist framework and library developers. Most of the time, these features are straight-forward, and you can simply use them; in other cases, however, we've run into behaviors that were unexpected. This post will detail several of these, so you either don't run into the same issues -- or can capitalize on some of our discoveries. Closures, Anonymous Functions, and Lambdas, oh my!Briefly, these are all synonyms (with slight contextual differences) for a single PHP construct, the anonymous function: $callback = function ($param) { // do something };
You can assign an anonymous function to a variable, or pass it in-line as a
callback argument to a function or method call. The construct makes for some
really flexible designs, and is particularly useful with the various array
functions and with We discovered one interesting issue, however. PHP does not like serializing closures; doing so raises an exception ("Serialization of 'Closure' is not allowed"). This has a number of implications:
Additionally, even though internally anonymous functions are represented
via the class Invokables
One fun new feature of PHP 5 is the magic method class Greeting { public function __invoke($name) { return "Hello, $name"; } } $greeting = new Greeting; echo $greeting('world'); // "Hello, world"
Unlike other magic methods, it actually is faster than the
alternatives. When simply returning a value, it's 25% faster than calling a
method on the same object; when used with
So, sounds like a great new feature, right? Yes... but there are some things you should know.
Namespacing for fun and profitPlease put aside your opinions on the choice of namespace separator in PHP; it's water under the bridge at this point, and there were good technical reasons for the choice. We have an implementation, so let's use it. First off, you declare your namespace at the top of your file: namespace Zend\Filter; Or you can have several namespaces in the same file, as long as you have no loose code: namespace Zend\Filter; // some namespaced code here... namespace Zend\Validator; // some namespaced code here... While the above is valid, the PHP manual recommends using braces if you're using multiple namespaces in a single file: namespace Zend\Filter { // some namespaced code here... } namespace Zend\Validator { // some namespaced code here... }
You can import code from other namespaces using the
namespace Foo; use Zend\Filter; use Zend\Validator\Int as IntValidator; $validator = new IntValidator; // Zend\Validator\Int if ($validator->isValid($foo) { $filter = new Filter\Int(); // Zend\Filter\Int echo $filter($foo); } Some quick rules about namespaces:
One discovery we made was that you can have a namespace that shares the same name as an interface of class. As an example: namespace Foo { interface Adapter { // definition here... } } namespace Foo\Adapter { use Foo\Adapter as FooAdapter; class Concrete implements FooAdapter { // ... } } This discovery has allowed us to define more "top-level" interfaces within components, with concrete implementations in a namespace matching the interface. This reduces some verbiage, defines a better class hierarchy, and makes the code relations more semantic. Finally, we've found that one huge benefit to namespaces is when unit testing: we can define a separate namespace for unit tests, as well as separate namespaces for each component. If we use these namespaces for test artifacts -- classes and mock adapters consumed by the unit tests -- we ensure that each test suite is fully encapsulated. This has led to fewer issues with naming collisions. In closing...PHP 5.3 offers a ton of new features -- those I go through here are but some of the more prominent ones. If you haven't started hacking with 5.3, you should -- it's definitely the future of PHP, and you'll be seeing an increasing number of libraries and frameworks using it. Trackbacks
Trackback specific URI for this entry
No Trackbacks
Comments
Display comments as
(Linear | Threaded)
Thanks for the greet overview to work on zf 2.0 and your experience with the new php version.
The problem i see is that it is not supported by some linux distribution which is installed on most webservers (eg debian lenny isn't supporting php5.3 by default by apt / aptitude). Please, let's not debate about whether you should be using PHP 5.3 or not based on distribution support. It will come eventually, and likely sooner rather than later as many prominent projects are already migrating to it.
I would add that the use keyword is fundamental also to include variables from the local scope; an example is at http://www.php.net/manual/en/functions.anonymous.php in the function Cart::getTotal().
Indeed! I'm replying solely to stress the usefulness of the "use" keyword in conjunction with lambda functions!
So many times have I had to code around the fact that many functions accepting callbacks as arguments do not allow passing more arguments to the callback. Behold the power of the new "use" keyword. Great job! p.s. although the same name, this is different than the "use" keyword for namespace aliasing Indeed -- I just used this feature today with array_map() -- $data = array_map(function ($value) use ($ts) { return $ts; }, $keys); -- set the values of an associative array all to the same value.
Great article.
BTW: Late static binding is also very prominent in my opinion. Absolutely -- but this post has to do with language features we've been using, benchmarking, and abusing so far.
LSB - wee, more like the root of all evil. ;(
More statics, more untestable code. And now it can do a whole lot more funky magic stuff on the inside. I am sorry...I laugh every single time I see the new namespace separator:
namespace Zend\Filter; Sorry...didn't read the "do not make fun of the namespace choice" rule before posting.
If you're using is_callable() to check if a variable is a closure, it might be worth adding is_object() as well. This weeds out certain strings and double arrays from passing the is_callable() test and then potentially causing errors (both from accidents and backwards developers).
Of course, this exposes some implementation details about closures, so it might be worth wrapping it all in an SomeUtilsClass::is_closure() function anyways. Wow, that __invoke behavior on object properties feels.. broken. You can't have a function called foo if you have a property foo to begin with, so why doesn't it work?
Yeah, I'm thinking of opening an issue report for that one. I'd completely understand an error happening if I had both a method and property of the same name, and the property was assigned an invokable object.. but in this particular case, it makes no sense.
You should have seen the stuff we had to do to modify tests to work... Hey -- there's a feature request to modify the behavior of this already, it appears: http://bugs.php.net/bug.php?id=50029 -- go vote now!
If you are looking for a framework that has already implemented php 5.3 and is the future of php, head to http://lithify.me and join the chat on irc: #li3@Freenode
I'm not going to pull your comment, but I would like to point out that I don't go trolling on the Lithium developers' blogs (or those of developers on other frameworks), directing people to use ZF.
To be honest, I'd rather see good interoperability between the various frameworks, so that we're not all re-inventing the wheel when there are perfectly good components elsewhere. This can be achieved through a combination of shared standards, good licensing, fewer ego battles between the developers, and less fanboi-ism on the part of the users. I'm really excited to get started using PHP 5.3 and beta-testing Zend Framework 2 when something becomes available. Keep up the great work!
In your code snippet above, showing how to use interfaces since type-hinting with __invoke is impossible, your code seems to have an error, namely
if ($filter implements CallableFilter) I think you probably meant to write if ($filter instanceof CallableFilter) I point this out in case anyone else tries out the snippet and is wondering why they keep getting an error message. Thanks for the tutorial which also shows why PHP keeps getting better and better. This magic method __invoke is one that I certainly look forward to trying out more. Thanks -- I've updated the code accordingly!
If you exclude "is_callable" objects from being serialized in order to avoid closures from being serialized, would that cause an object of any class that implements __invoke to not be serialized?
PHP has no issues with serializing invokable objects -- only closures.
For ZF, this simply meant that when we were unit testing, we had to be careful not to use closures with spl_autoload -- any other type of callback worked fine. In userland, you need to be careful anytime you try to serialize objects/data structures that might contain closures. Again, only Closures affect the functionality. Yes, but in your spl_autoload function, how do you differentiate a closure and an object that implements __invoke if both pass the is_callable test?
Manually. Once we ran into the issue, we simply altered our codebase so that we weren't passing closures to spl_autoload within our test suite. The issue we were having was that we our unit test bootstrap was originally registering a closure with spl_autoload, and when testing Zend\Loader\Autoloader, we were retrieving the registered autoloaders in order to test manipulations to the spl_autoload registry; doing so raised the "cannot serialize Closures" issues. By simply altering our test suite to remove closures when manipulating spl_autoload, we avoided the issue. And, as noted, if you're using the most recent version of 5.3, the issue is corrected.
You can also test if a callback is an object, and of class "Closure" -- this returns true only for closures, not invokable objects (is_object($closure) && 'Closure' == get_class($closure)). However, this may not be future-proof; the manual indicates that this is an implementation detail and should not be relied upon. Matthew,
You mentioned in the article that the exception "Serialization of 'Closure' is not allowed" goes away in php 5.3.2. I've just verified that this exception is still thrown (on macosx w/ php 5.3.2 via macports and PHP 5.3.2 (cli) on Windows 7) if you pass a closure to spl_autoload_register. In other words, you still can't do this (for those trying to work with PHP 5.3 features and ZF 1.10.x): $autoLoader->setDefaultAutoloader(function($className){ //... }); Thanks for this article...saved me a couple hours of research. Just wanted to thank you for the article! It brings the more advanced features of php 5.3 to life.
I developed a php 5.3 application for a windows server, partly based on ZF 1.9.5 (use at will). One of the issues encountered there was the MySQL connectivity bug in php 5.3.1: it could not resolve 'localhost' on windows. php 5.3.0 did not have this issue. Another great feature (very easy to use) is the DateTime class, but I use ZF to localize the dates. I was wondering, will ZF 2.0 be BC so that you can still use new Zend_Validate_Int; or will we have to rewrite everything? (to beconme Zend\Validate\Int ?) Since we're moving to namespaces, we're breaking BC basically entirely. However, we're creating a map of old classnames -> new namespaced versions, and already have a prototype tool for running this over your code to rewrite it to work with the new code (we use it on our test cases, actually!). It's not 100% perfect, but it does a fair bit of the job. Additionally, we're making a note of any explicit BC breaks in components -- renamed methods, removed methods, new interfaces, etc. -- with the hope of creating a step-by-step migration guide for end users.
BTW, DateTime exists in 5.2.0 and up -- but, as you note, cannot be used to localize dates. Obviously, with very little time on my hands, I'm not looking forward to the pain involved in migrating
I remember I tried the DateTime class on a php 5.2.6 installation and it wasn't as complete as it is in 5.3. That's why I upgraded in the first place! I benchmarked the anonymous functions. Here's what I discovered: bit.ly/bAjA6k
I read your post, but have to disagree with you on your assessment, as you missed one key difference between closures and create_function() that has a huge impact: closures can be cached by an opcode cache. This means that while your initial declaration may take a few _milliseconds_ more, if you are using an opcode cache, that is the _only_ hit you will take. And frankly, most applications should not be worried about a difference in milliseconds -- not unless you're the size of Y!, Digg, or the like.
Some of us speak in acronyms and some of us do better with whole words. If anyone is confused and wondering whether BC is a reference to PHP's BC Math module, the answer is negative. BC is an acronym for "backwards compatibility" and apparently there are some issues in PHP5.3 related to features like namespaces. I also would agree that if there are such issues, I'm okay with that in exchange for having namespaces.
Add Comment
|
Calendar
QuicksearchLinksArchivesCategoriesSyndicate This BlogShow tagged entries |
|||||||||||||||||||||||||||||||||||||||||||||||||




