LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Tuesday, April 6. 2010

A Primer for PHP 5.3's New Language Features

For 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 preg_replace_callback(). If you see any "create_function" constructs in your codebase, go and replace them immediately with anonymous functions; not only will they be easier to read (escaping code content in create_function() was always a pain), but they'll be much faster, and also benefit from opcode caching if available.

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:

  • If you need to alter the SPL autoloader stack, be careful about using closures with it. As an example, our testbed was caching the autoloaders by storing the return value of spl_autoload_functions(), and then resetting it during testing. Unfortunately, if you register a closure with spl_autoload_register, you may get an error when you do this. (Note: this appears to be fixed with 5.3.2 and up.)
  • If you are serializing classes that have properties that reference closures, you will need to add some logic to __sleep() and __wakeup() to ensure those properties are not serialized, and to recreate them on deserialization.

Additionally, even though internally anonymous functions are represented via the class Closure, you cannot typehint on that class; the only way to test if a variable is a closure is to use is_callable().

Invokables

One fun new feature of PHP 5 is the magic method __invoke(), which allows you to call an object as if it were a function:


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 call_user_func_array(), it's 30% faster than using a normal, array-style callback (e.g., array($o, 'greet') -- even when it's proxying to another method!

So, sounds like a great new feature, right? Yes... but there are some things you should know.

  • Like closures, you cannot typehint explicitly for __invoke(); you have to either use is_callable() or create an interface defining it:

    interface Filter
    {
        public function filter($value);
    }

    interface CallableFilter
    {
        public function __invoke($value);
    }

    class IntFilter implements Filter, CallableFilter
    {
        public function filter($value)
        {
            return (int) $value;
        }

        public function __invoke($value)
        {
            return $this->filter($value);
        }
    }

    $filter = new IntFilter;
    if ($filter instanceof CallableFilter) {
        // matches
    }
     
  • Be careful about using objects implementing __invoke() as object properties; they don't do what you expect. For instance, consider the following:

    class Foo
    {
        public function __invoke()
        {
            return 'foo';
        }
    }

    class Bar
    {
        public $foo;

        public function __construct()
        {
            $this->foo = new Foo;
        }
    }

    $bar = new Bar;
    echo $bar->foo();
     
    You might expect this to echo "foo" -- but it won't. Instead, it'll raise an E_FATAL, claiming "Call to undefined method Bar::foo()". If you want to execute the property, you have to assign it to a temporary variable first, or explicitly call __invoke():

    $foo = $bar->foo;
    echo $foo();

    // or:

    $bar->foo->__invoke();
     

Namespacing for fun and profit

Please 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 use construct. This construct also allows you to alias the namespace (or class, constant, or function within the namespace) using the as modifier:


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:

  • Fully qualified namespaces (FQN) begin with a namespace separator ("\"). Classes, functions, constants, and static members referenced using a FQN will always resolve.
  • The namespace declaration is always considered fully qualified, and should not be prefixed with a namespace separator.
  • Namespaces referenced in a use statement are always considered fully qualfied; you can prefix with a namespace separator, but it's not necessary.
  • When referring to namespaced classes within a namespace, be aware of the origin: if you don't fully qualify the namespace, the assumptions will be:
    • A sub-namespace of the current namespace
    • A reference to one of the aliases defined when importing
    For example, consider the following code:

    namespace Foo;
    use Zend\Filter; // imports are always considered FQN

    $foo       = new Bar\Baz;             // actual; Foo\Bar\Baz
    $filter    = new Filter\Int;          // actual; Zend\Filter\Int
    $validator = new Zend\Validator\Int;  // actual: Foo\Zend\Validator\Int
    $validator = new \Zend\Validator\Int; // actual: Zend\Validator\Int
     

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.

Posted by Matthew Weier O'Phinney in PHP at 11:10 | Comments (31) | Trackbacks (0)
Defined tags for this entry: oop, php, zend framework
Related entries by tags:
Autoloading Benchmarks
Applying FilterIterator to Directory Iteration
Running mod_php and FastCGI side-by-side
Creating Zend_Tool Providers
State of Zend Framework 2.0

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).
#1 Tobias (Link) on 2010-04-06 11:31 (Reply)
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.
#1.1 Matthew Weier O'Phinney (Link) on 2010-04-06 11:32 (Reply)
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().
#2 Giorgio Sironi (Link) on 2010-04-06 14:04 (Reply)
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
#2.1 Bogdan on 2010-04-15 15:36 (Reply)
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. :-)
#2.1.1 Matthew Weier O'Phinney (Link) on 2010-04-15 16:09 (Reply)
Great article.

BTW: Late static binding is also very prominent in my opinion.
#3 Nexik (Link) on 2010-04-06 14:22 (Reply)
Absolutely -- but this post has to do with language features we've been using, benchmarking, and abusing so far. :-)
#3.1 Matthew Weier O'Phinney (Link) on 2010-04-06 14:38 (Reply)
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.
#3.2 foo on 2010-04-06 19:45 (Reply)
I am sorry...I laugh every single time I see the new namespace separator:

namespace Zend\Filter;
#4 Robert on 2010-04-06 21:53 (Reply)
Sorry...didn't read the "do not make fun of the namespace choice" rule before posting.
#5 Robert on 2010-04-06 22:00 (Reply)
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.
#6 Ross on 2010-04-07 03:40 (Reply)
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?
#7 Jani Hartikainen (Link) on 2010-04-07 05:08 (Reply)
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...
#7.1 Matthew Weier O'Phinney (Link) on 2010-04-07 07:37 (Reply)
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!
#7.2 Matthew Weier O'Phinney (Link) on 2010-04-07 07:49 (Reply)
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
#8 Cody (Link) on 2010-04-08 23:17 (Reply)
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.
#8.1 Matthew Weier O'Phinney (Link) on 2010-04-09 07:37 (Reply)
Amen!
#8.1.1 Michael (Link) on 2010-07-17 10:34 (Reply)
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!
#9 Brian Reich (Link) on 2010-04-09 07:48 (Reply)
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.
#10 Sharon Levy on 2010-04-14 03:35 (Reply)
Thanks -- I've updated the code accordingly!
#10.1 Matthew Weier O'Phinney (Link) on 2010-04-14 12:14 (Reply)
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?
#11 Judah Anthony on 2010-04-14 12:01 (Reply)
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.
#11.1 Matthew Weier O'Phinney (Link) on 2010-04-14 12:12 (Reply)
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?
#11.1.1 Judah Anthony on 2010-04-14 12:37 (Reply)
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.
#11.1.1.1 Matthew Weier O'Phinney (Link) on 2010-04-14 13:17 (Reply)
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.
#11.1.1.1.1 Wil Moore III (Link) on 2010-06-30 12:36 (Reply)
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 ?)
#12 Bart McLeod (Link) on 2010-04-15 16:10 (Reply)
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.
#12.1 Matthew Weier O'Phinney (Link) on 2010-04-15 16:16 (Reply)
Obviously, with very little time on my hands, I'm not looking forward to the pain involved in migrating :-) , but I remember I voted for BC breaking in favor of using new PHP 5.3 features. We have to move on.

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!
#12.1.1 Bart McLeod (Link) on 2010-04-15 17:07 (Reply)
I benchmarked the anonymous functions. Here's what I discovered: bit.ly/bAjA6k
#13 David Boskovic (Link) on 2010-04-26 05:03 (Reply)
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.
#13.1 Matthew Weier O'Phinney (Link) on 2010-04-27 07:39 (Reply)
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.
#13.1.1 Sharon Lee Levy (Link) on 2010-06-30 19:03 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Twitter
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back September '10
Mon Tue Wed Thu Fri Sat Sun
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Planet PHP
  • Zend Framework, where I'm project lead
  • Sebastian Bergmann
  • Cal Evans
  • Shahar Evron
  • Paul M. Jones
  • Bill Karwin
  • Mike Naberezny
  • Fabien Potencier
  • Ben Ramsey
  • Derick Rethans
  • Ralph Schindler
  • Marco Tabini

Archives

September 2010
August 2010
July 2010
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml apache
xml best practices
xml books
xml conferences
xml cw09
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml git
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml rest
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zendcon09
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net