Categories


Archives


Recent Posts


Categories


PHP Magic Methods and Class Aliases

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Subtitle: PHP Has Mysterious Ways. This week it’s another quick primer on PHP’s lesser known “magic” features in preparation of moving on to Laravel Facades.

PHP Class Definition

PHP’s object system is class based. If you want to create an object, you need to tell your program which class PHP should use to instantiate the object. Consider the following code which defines a class Foo, and then instantiates an object from Foo.

<?php    
class Foo
{
}

$object = new Foo;

PHP has a generic object, but even that object has a class (stdClass below)

<?php    
//these are equivalent
$object = new stdClass;
$object = {};

Class definition in PHP is a very static affair. When you use the class keyword and start a class block, PHP enters a special parsing mode where it’s only looking for class properties, constants, and methods. For example, the following is invalid PHP, and will raise a halting PHP Parse error

<?php        
class Foo
{
    $time = time();
    protected $_time = $time;
}

To a PHP or Java developer, this seems obvious. However, to programmers from languages like ruby and python, this is restrictive. In those languages a class statement is just another block of code. A lot of the “magic”, or “meta-programming” in these languages relies on this.

Fortunately (or unfortunately, depending on your point of view), PHP classes have some meta-programming functionality baked in. The specific features we’re interested in today are PHP’s “magic methods”.

Magic Methods

If you try to execute the following code

<?php        
class Foo
{
}
$object = new Foo;
$object->someMethod();

PHP will issue a PHP Fatal error: Call to undefined method Foo::someMethod()

That’s because we’ve called the method named someMethod, but the class Foo doesn’t have someMethod defined. However, consider the following

<?php        
class Foo
{
    public function __call($method, $args)
    {
        echo "Called __call with $method","\n<br>\n";
    }
}

$object = new Foo;
$object->someMethod();

Here we’ve defined a goofy looking method named __call, and our class still doesn’t have a method named someMethod. However, running the above program results in the following output

Called __call with someMethod

That is, our program finishes executing without issuing a call to a nonexistent method error.

That’s because __call is a magic method. In PHP, if you define a method named __call, and a programmer calls a method that doesn’t exist (or is access restricted), PHP will call the __call method instead of failing with an error.

The __call method’s two arguments are

  1. The method name the client-programmer tried to call
  2. An array of arguments the client-programmer tried to pass this method.

This is a powerful feature that allows a PHP programmer to decide what happens when a client-programmer calls a method on their object. For the Magento developers reading, this __call method is how Magento’s setter and getter methods are implemented, and why you can do something like this,

<?php            
$object->setSomeField('value');
echo $object->getSomeField();    

without defining a setSomeField and getSomeField method. The tradeoff, like all meta-programming features, is clarity and performance.

If a client-programmer isn’t familiar with the magic methods, or isn’t familiar with your implementation of the magic methods, they may not realize what’s going on with simple methods calls, and be confused when then can’t find a method definition.

The __call method also invokes a slight performance penalty, which can be multiplied by inefficient code in __call, which can be multiplied again by client programmers using magic methods like candy without realizing there’s a performance hit.

Static Magic Methods

Next, give the following small program a try

<?php            
class Foo
{
    static public function someStaticMethod()
    {
        echo "Called";
    }

    public function __call($method, $args)
    {
        echo "Called __call with $method","\n<br>\n";
    }    
}
Foo::someStaticMethod();

Here we’ve called the static method someStaticMethod. In PHP, classes can have static methods. If you haven’t encountered them before, a static method “belongs” to a class. That is, it’s a way to define a function on a class that doesn’t know about an object’s values, and can be called without instantiating an object. Static methods can’t access normal object properties, but then can access static object properties.

Static methods have a bad reputation in object oriented programming circles because early java developers used them to recreate programming patterns that were more C like than java like. They also, from a certain point of view, allow you to introduce global state into your object. However, that’s another topic for a different article.

There’s also some additional confusion around static methods in PHP circles, because versions of PHP prior to 5.0 allowed you to call any method on a class with a static like syntax. The following was valid PHP 4 code

<?php            
//valid PHP 4
class Foo
{
    function someMethod()
    {
        echo "Called","\n";
    }
}
Foo::someMethod();

The idea here was to give PHP 4 developers the ability you use non-state-dependent object methods without the need to instantiate an object (a common use of static methods). While this will raise a PHP Strict standards error in modern versions of PHP, the PHP core team’s policy of maintaining backwards compatibility at all (most?) costs means this is still working code in modern versions of PHP. That said, if you’re thinking about using these fake static calls in your system — you’re reading the wrong blog.

So, why mention static methods? Consider the following program

<?php            
class Foo
{
    public function __call($method, $arguments)
    {
        echo "Called $method","\n";
    }
}
$foo = new Foo;
$foo->someMethod();

Foo::someStaticMethod();

The above program will produce the following error

PHP Fatal error: Call to undefined method Foo::someStaticMethod()

That is, the magic __call method intercepts our call to someMethod. However, it does not intercept a call to someStaticMethod. It turns out that __call only works with instance methods (instance methods are the normal methods available to an object — i.e. declared without the static keyword).

Fortunately, the’s a magic method specifically for static methods. Give the following a try

<?php            
class Foo
{
    public function __call($method, $arguments)
    {
        echo "Called $method","\n";
    }

    static public function __callStatic($method, $arguments)
    {
        echo "Called $method","\n";
    }

}

Foo::someStaticMethod();

The __callStatic method works just like __call, except it’s for static methods.

The main takeaway here is if you see code that’s calling methods that don’t seem to exist anywhere in an object’s class hierarchy, there’s a good chance you’ll find a magic method somewhere in the class hierarchy.

Class Aliases

Consider the following (real) class name from Magento, (a PHP based ecommerce system)

<?php            
class Enterprise_SalesArchive_Block_Adminhtml_Sales_Archive_Order_Container extends Mage_Adminhtml_Block_Widget_Grid_Container
{
}

That’s a comically long classname. However, prior to PHP 5.3 and the introduction of namespaces, it was common to see class names like this. The PHP function class_alias seems like it was created to help deal with this. Using class_alias, you can include code like this in your application bootstrap,

<?php
class_alias('Enterprise_SalesArchive_Block_Adminhtml_Sales_Archive_Order_Container','ArchiveOrderContainer');

and then whenever you refer to the shorter class ArchiveOrderContainer

<?php            
$object = new ArchiveOrderContainer;

PHP will assume you want the class Enterprise_SalesArchive_Block_Adminhtml_Sales_Archive_Order_Container and instantiate that instead. That is, ArchiveOrderContainer becomes an alias for the real class name.

One downside of aliasing is there’s no way, short of reflection, to get a list of currently defined aliases. If you’re browsing PHP code you didn’t write, you can use the following reflection code to figure out what a class’s “true” name is

<?php            
class A
{
}

class_alias('A', 'B');

$object = new B;

$r = new ReflectionClass('B');
var_dump($r->getName());

The need for class aliasing has been mostly solved by PHP namespacing (also introduced in PHP 5.3), but there’s still some frameworks that will use aliasing for meta-programming hijinks. If you’re going to spend time in the bowels of PHP frameworks, you’ll want to add class_alias to the list of things to look out for.

Next time we’ll be diving into the bowels of Laravel’s Facade code, and the need for this quick primer will become more obvious.

Originally published September 29, 2014
Series Navigation<< Binding Objects as Laravel ServicesUnraveling Laravel Facades >>

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 29th September 2014