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
- The method name the client-programmer tried to call
- 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.