PHP Magic Methods and Class Aliases

Like this article? 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.

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::someMethod();

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

Binding Objects as Laravel Services

Like this article? 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.

Last time we discussed instantiating objects in Laravel using the make method. Today we’re going to talk about services, service containers, and binding objects into service containers.

Before we do that, we should briefly discuss service oriented architecture.

This article, and series, is focused on exploring Laravel’s internals. Many of the code samples are optimized to help you understand how Laravel is built, and may not be a best practice for day-to-day application development.

Service Oriented Architecture

Wikipedia defines Service Oriented Architecture as

Service-oriented architecture (SOA) is a software design and software architecture design pattern based on distinct pieces of software providing application functionality as services to other applications. This is known as service-orientation. It is independent of any vendor, product or technology

This is as good a definition as any. There may have been a time when “Service Oriented” refereed to a specific set of technologies, but at this point it’s a generic term that applies to a general approach to application development and/or code organization in a single application.

The best way to think about services is as an approach to programming where a service is some code that does a thing that’s not directly related to your application. A very basic example is a database querying service. If you’re writing an application to show cute cat pictures on the internet, your objects that describe a cat’s cuteness shouldn’t contain code for connecting to the database, parameterizing query strings, etc. Keeping your code for talking with a database hidden behind a service means you can concentrate on cat logic, and leave the tricky programming problems involved with talking to a database to the database service developers.

The current fashion in PHP framework development is heavily based on SOA ideas, and if you want to start thinking like a Laravel core developer you’ll want to start adopting the mindset of someone who implements services (as opposed to someone who uses services to build cute cat websites).

Service Containers

Like all abstract concepts, eventually you need to create an implementation for your service based system. The Symfony 2 project is a driving force behind popularizing the service container approach, although the idea’s been floating in the air for several years now.

A service container is an object that’s either globally available or easily “fetch-able” via anywhere you’d need to use services. A service container contains key/value pairs where the key is a service name, and the value is an object that implements a service.

If you’ve ever used Symfony 2, you’re likely familiar with service calls that look something like this

$this->get('my_mailer');

$this->getContainer()->get('my_mailer');

$this->container->get('my_mailer');

As the service user (or “client programmer”), you fetch a reference to the service container object, and then tell the service container object you want a named service (my_mailer above).

Laravel also uses a service container, but with a few opinionated differences. First, in Laravel, the global Application object is the server container. If Laravel had a my_mailer service, you could access it by getting an application object reference

$app = app();

And then using PHP’s ArrayAccess syntax on the application object to fetch the service by name.

$mailer = $app['my_mailer'];

After last week’s primer you’re probably chomping at the bit to checkout Laravel’s ArrayAccess implementation. We’ll get there eventually, but for now just treat it as the magic syntactic sugar that makes code for fetching a service object more compact.

Let’s look at a real service. A stock Laravel 4.2 system comes with a db service. If you use this service, you can run SQL queries against a database. Assuming you’ve setup Laravel to connect to a MySQL database, you can use the following code to run a SHOW TABLES query against the database.

$app = app();
$result = $app['db']->select('SHOW TABLES');
var_dump($result);

If you use var_dump to look at the db service object.

var_dump($app['db']);

you’ll see the service container returns an Illuminate\Database\DatabaseManager object

object(Illuminate\Database\DatabaseManager)[93]
  protected 'app' => 
  ...

This object is an implementation of the db service. One advantage of services is they’re another way to implement an “inversion of control” system. While Illuminate\Database\DatabaseManager is the current service implementation, a future version of Laravel, or a specific Laravel application, may use a different db service.

If you’re an experienced Laravel developer, you know there’s other ways to access services that don’t involve calling the app function. We’re working up to those methods, we will get there eventually, but for today we’re sticking with calls to app.

Creating a new Service

So, the question that’s likely on many of your lips is

How do services get inside the application container

This is where binding enters the story. Binding is one of those overloaded terms that means a variety of different things on different platforms. In Laravel, you bind a service to the Application. Binding a service to the application means other developers can access your service via the application object’s $app['service_name'] syntax.

From a high level that’s the only thing binding accomplishes. It’s nothing more complicated than that. Any first level magic user can cast this spell, no components needed. To prove it, we’re going to create a helloworld service. Our end goal will be the ability to run code that looks like the following

$app['helloworld']->sayHello();

and have it echo out the ancient and ubiquitous text Hello, World!.

The high level steps you need to take when creating a service are

  1. Pick a name (in our case, helloworld)

  2. Create an implementation class

  3. Bind the implementation class into the application container

Laravel has a lot of built-in classes and systems for creating packages and service providers — but in this tutorial we’re going to skip all that and concentrate on the most dead simple way to bind a service into the application container. You might use our techniques below to add a service to a single application, but you’d never use it to distribute your service to other Laravel systems.

If you’re interested in learning the right way to distribute your Laravel code, you’ll want to investigate Laravel Service Providers. Service Providers are outside of our scope for this series.

Creating the Implementation Class

We’ve already picked a name for our service (helloworld). The next step is to create an implementation class. You can name this class anything you’d like, and place is anywhere that Laravel will autoload classes from. We’re going to name our class Pulsestorm\Example, and to make things simple we’ll drop it in the app/models folder. If you’re new to Laravel, app/models is one of the default folders the autoloader will load classes from.

Create the following file in the following folder

#File: app/models/Pulsestorm/Example.php
<?php
namespace Pulsestorm;
class Example
{
    protected $_times = 0;
    public $message = "Hello World!  We've met %d times!";
    public function sayHello()
    {        
        echo sprintf($this->message, $this->_times);
        $this->_times++;
    }
}

Here we have a simple class. This class implements a hello world service. There’s no special classes to inherit from or interfaces to implement — any class can be a service class. It’s good OOP practice to have service classes implement interfaces or extend from abstract classes, but Laravel doesn’t require it.

Before we bind our class as a service, let’s test it out. Add the following simple route configuration to the end of app/routes.php.

#File: app/routes.php
Route::get('/service-tutorial', function()
{
    $object = new Pulsestorm\Example;    

    $object->sayHello();    
    echo "\n<br>\n";
    $object->sayHello();    
});

And then load the route in your web browser (using your own application URL, of course)

http://laravel.example.com/service-tutorial

You should see output something like this

Hello World! We've met 0 times! 
Hello World! We've met 1 times!

That is, the sayHello method outputs the Hello World text, and also keeps track of how many times we’ve called the sayHello method.

With our class working, we’re ready to move on to the service binding.

Binding the Service

As a reminder, binding a service means we’re making it available to users in the application service container. All binding a service requires us to do is call the Application object’s bind method. We’re going to bind our service in the app/start/global.php file. This file runs during the application bootstrap process (although this might change in Laravel 5). By binding our service here it will be available to the rest of the application.

After all this buildup, binding a class is relatively anti-climatic. Just add the following line to the end of your app/start/global.php file

#File: app/start/global.php

$app = app();
$app->bind('helloworld', 'Pulsestorm\Example');

That’s it. The first argument to bind is the name you picked for your service. The second is the name of your implementation class (passed as a string).

With the above in place, let’s try calling our service from a route. Change your service-tutorial route so it matches the following.

#File: app/routes.php
Route::get('/service-tutorial', function(){           
    $app = app();
    $app['helloworld']->sayHello();
    echo "<br>";
    $app['helloworld']->sayHello();
});

And then load your route in your browser.

http://laravel.example.com/service-tutorial

If you’ve set things up correctly, you should see the following output

Hello World! We've met 0 times!
Hello World! We've met 0 times!

Success! You’ve just created your first Laravel service.

Shared Services

Let’s take another look at our output from above

Hello World! We've met 0 times!
Hello World! We've met 0 times!

You may have noticed one difference from before — the We've met n times string didn’t increment. It echoed zero both times. That’s because whenever you ask the service container for this service ($object = $app['helloworld']), Laravel will instantiate a new object for you. This makes sense as the default behavior, as it discourages developers for using services to store application state.

Sometimes though, as a service developer, you don’t want to instantiate a new object. The database service we used earlier is a classic example of this. If we instantiated a new database service every time we asked the application for a service, that would mean a new database connection would be instantiated every time we ran a query. A page with ten queries would try to connect to the database 10 times, and with a database connection being such an expensive operation, you’d quickly hit scaling or performance problems.

Fortunately, Laravel has the concept of “shared” services. If a service is a shared service, it means Laravel will always return the same instance whenever you ask for the service. In some object systems this concept is called a singleton.

If you didn’t follow that, an example should clear it up. To tell Laravel you want your service to be a shared service, you simply pass in a third parameter at bind time. Change your application binding so it looks like the following.

#File: app/start/global.php

$app = app();
$app->bind('helloworld', 'Pulsestorm\Example', true);

See that third parameter? The boolean true? If you look at bind’s method prototype

#File: vendor/laravel/framework/src/Illuminate/Container/Container.php
public function bind($abstract, $concrete = null, $shared = false)
{       
    //...
}

You can see the third parameter is named $shared. This parameter tells bind we want a shared service.

With the above parameter added to the bind method call, try reloading your page. You should see the following

Hello World! We've met 0 times!
Hello World! We've met 1 times!

As you can see, the service is keeping track of state between requests, and correctly incrementing the “times met” property.

Important: Having our hello world service track state like this would be a bad implementation of service oriented architecture — in general the only state a service should track is state it needs to do its job, not state to implement application functionality.

There’s no hard and fast rules as to when you should use a shared service. The services Laravel itself ships with are a combination of shared and unshared. You’ll need to use your best judgment when creating your own service. If you need an algorithmic methodology, stick to not using shared services until you start noticing performance problems with your service. Then, consider a shared service to solve your performance problems.

Binding Closures

If we take another look at our bind call

#File: app/start/global.php
$app = app();
$app->bind('helloworld', 'Pulsestorm\Example', true);

Another, more subtle, problem pops out. Because we’re passing our class name in as a string, there’s no chance for us to do any initialization of our service object. Fortunately, Laravel has a solution for this. In addition to accepting a string, the bind method also accepts a PHP anonymous function as an argument. Try replacing your binding code with the following.

#File: app/start/global.php
$callback = function(){
    $implementation = new Pulsestorm\Example;
    $implementation->message = 'Hello world, we meet again (time number %d)';
    return $implementation;
};

$app = app();
$app->bind('helloworld', $callback);

If you reload your page you should see the following text.

Hello world, we meet again (time number 0)
Hello world, we meet again (time number 0)    

That is, we’ve successfully bound a service with an anonymous function. Here’s how the above code works. First, and most importantly, we define an anonymous function

#File: app/start/global.php
$callback = function(){
    $implementation = new Pulsestorm\Example;
    $implementation->message = 'Hello world, we meet again (time number %d)';
    return $implementation;
};

This function should return an instance of our service implementation. Since we’re instantiating the object ourselves, this gives us a chance to perform a number of initializations steps. (i.e. setting the messages property).

Once we’ve created this anonymous function, we pass it into bind

#File: app/start/global.php
$app = app();
$app->bind('helloworld', $callback);

Once we’ve done this, the next time we request the helloworld service, Laravel will invoke/call the anonymous function — whatever the anonymous function returns is what our request for a service will return.

Shared Service Bindings with Closures

In our example above we omitted the third parameter from bind. This meant our service was an un-shared service, and returned new objects each time we requested a service from the application object. Services created with anonymous functions are eligible to be marked as shared, same as any other service. If we wanted a shared service, providing the third method parameter would work

#File: app/start/global.php
$app->bind('helloworld', $callback, true);

However, if you’re binding anonymous functions as services, you have another option for creating a shared service, and that’s the bindShared method. Replace your binding code with the following

#File: app/start/global.php
$callback = function(){
    $implementation = new Pulsestorm\Example;
    $implementation->message = 'Hello world, we meet again (time number %d)';
    return $implementation;
};

$app = app();
$app->bindShared('helloworld', $callback);

All we’ve done here is changed the call to the $app->bind method into a call to the $app->bindShared method. Reload with the above in place, and we’ve got our shared/singleton behavior back

Hello world, we meet again (time number 0)
Hello world, we meet again (time number 1)    

The bindShared method is the preferred way of assigning a shared service to the container, and the Laravel core code uses it extensively. As a quick example, take a look at this service provider code where Laravel binds the database service

#File: vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php
//...
    $this->app->bindShared('db', function($app)
    {
        return new DatabaseManager($app, $app['db.factory']);
    });    
//...

Here the Laravel core users bindShare to bind a DatabaseManager object as the db service.

Non-Object Services

There’s one curious side effect to Laravel’s anonymous function service providers. Consider a service bound like this

#File: app/global/start.php
$app->bindShared('curious', function(){
    return 'Curious Service is Curious';
});    

and used like this

#File: app/routes.php
Route::get('/service-tutorial', function(){           
    $app = app();
    $curious = $app['curious'];

    var_dump($curious);
});

The above code will output the text

string 'Curious Service is Curious' (length=26)

That is, despite us requesting a service object, the service container returns a string. It’d be easy to write this “string service” off as an unintended side effect of Laravel’s anonymous function implementation, but the core application code itself relies on this behavior. Give the following a try

#File: app/routes.php
Route::get('/service-tutorial', function(){           
    $app = app();
    var_dump($app['env']);
});

Loading the tutorial route in a browser with the above code results in output something like the following

string 'local' (length=5)

Your system may output the text production, or development, or something else. This env service is how Laravel keeps track of the current environment, and knows which configuration files to load. It’s also why the resolving listener in our last article received a string instead of an object.

The takeaway here is that despite being billed as a service object container, Laravel’s service container will sometimes return a scaler value (boolean, string, int, etc). In other words, you can’t assume it will always return an object. You’ll want to be defensive in your resolvingCallbacks, or anywhere you’re not familiar with the behavior of a particular service.

Laravel Services, make, and ArrayAccess

There’s one last thing we’ll want to cover about services before we finish up for today. In the first article of this series, we covered using the application object’s make factory to create objects. What we didn’t mention in that tutorial was that make, in addition to accepting class names, also accepts service names.

Assuming you’ve bound a service to helloworld, give the following a try.

#File: app/routes.php
Route::get('/service-tutorial', function(){           
    $app = app();
    $app->make('helloworld')->sayHello();
    echo "<br>\n";
    $app->make('helloworld')->sayHello();
});

This will produce the exact same results as asking the application container for the service

Hello world, we meet again (time number 0)
Hello world, we meet again (time number 1)

The call to make also obeys any shared binding. Put another way, you can use make to create a Laravel object from a PHP class, or from a service container alias.

This is more than side effect behavior. So far we’ve accessed services via the app container’s ArrayAccess syntax.

$app['helloworld'];

When you try to get a value from an object with array syntax, PHP calls the object’s offsetGet method. If we take a look at the Container class’s offsetGet method (the Application class extends from the Container class), we see the following.

#File: vendor/laravel/framework/src/Illuminate/Container/Container.php    
public function offsetGet($key)
{
    return $this->make($key);
}

If it’s not immediately obvious to you — what the code above means is whenever we’ve called $app['helloworld'], behind the scenes Laravel’s just called $app->make('helloworld');
While interesting to systems developers, this creates a lot of confusion as to what “the right” way to access a Laravel service is, or how a service package developer should tell their users to access the service. We’ll cover this, and more, in our next article, when we talk about (cue nerd fight) Laravel Facades.

Originally published September 21, 2014

PHP ArrayAccess

This week’s a quick remedial primer on PHP’s ArrayAccess interface. We’ll be back to Laravel next time, and promise you’ll see why we needed the primer.

First, some background and context. PHP has a native language type called an array. This isn’t a C array, where each element points to a specific memory location. Instead, a PHP array is an implementation of an ordered map. You can add any variable to an array. If you want to treat an array as an ordered list, you can add items with the following syntax

$array = [];
$array[]  = 'abc';
$array[]  = 123;
$array[2] = 'science';    

You can also treat an array as a hash-map/dictionary

$array = [];
$array['foo'] = 'bar';
$array['baz'] = 42;    

PHP also has a native language type called an object. To create an object, a user programmer must first define a class with the class keyword

class Foo
{
}

and then use the new keyword to create an object from the class

$object = new Foo;

PHP also includes a built in class called stdClass. A user programmer can use this class to create stand-alone objects without defining a class.

$object = new stdClass;

Finally, more modern versions of php include a shortcut for creating stdClass objects

$object = {};

Array vs. Object

Objects and arrays are different things in PHP, although they share some functionality. You can use an array as a dictionary like data structure

$array = [];
$array['foo'] = 'bar';
$array['baz-bar'] = 'science';

echo $array['foo'];

You can also use an object as a dictionary like data structure

$object = {};
$object->foo = 'bar';
$object->{'baz-bar'} = 'science'

echo $object->foo;

The only difference is the syntax. Arrays use the bracket characters [], while objects use the “arrow” operator (->). If you try to use array brackets to access the values on on object, PHP will issue a fatal error

Fatal error: Cannot use object of type stdClass as array …

So far, most of this is common knowledge to working PHP programmers. What’s less commonly known is PHP allows end-user-programmers to define what should happen when a programmer tries to access object properties with array syntax. Put another way, you can give your objects the ability to work with array brackets ($o[])

Array Access

PHP ships with a number of built in classes and interfaces. These classes and interfaces allow a PHP developer to add features to the language that would normally be reserved for a PHP core developer. The one we’re interested in today is ArrayAccess.

If you want to give your object the ability to act like an array, all you need to do is implement the built-in ArrayAccess interface. Give the following simple program a try

<?php
class Foo implements ArrayAccess
{
}

$object = new Foo;
$object['foo'] = 'bar';        

echo $object['foo'],"\n";

In our class declaration statement we’ve included implements ArrayAccess. If we run this program, we’ll see the following error.

PHP Fatal error: Class Foo contains 4 abstract methods and must therefore be declared abstract or implement the remaining methods (ArrayAccess::offsetExists, ArrayAccess::offsetGet, ArrayAccess::offsetSet, …) in

Simply implementing an interface isn’t enough — we actually need to write the methods that are part of the interface synopsis. For a user defined interface, this would mean looking at the interface definition — for a built-in interface, we need to rely on the PHP manual. If you look at the manual, you’ll see the interface is defined as

ArrayAccess {
    /* Methods */
    abstract public boolean offsetExists ( mixed $offset )
    abstract public mixed offsetGet ( mixed $offset )
    abstract public void offsetSet ( mixed $offset , mixed $value )
    abstract public void offsetUnset ( mixed $offset )
}

That is, we need to define the 4 methods ArrayAccess::offsetExists, ArrayAccess::offsetGet, ArrayAccess::offsetSet, ArrayAccess::offsetUnset. Unfortunately, the help stops there. Interfaces are great for telling you what method you need to define, but less great at telling you what each method is supposed to do.

Let’s do a little detective work. Create the following short program, and then run it (either in your browser or via the command line)

<?php
class Foo implements ArrayAccess
{
    public function offsetExists ($offset)
    {
        echo __METHOD__, "\n";
    }

    public function offsetGet ($offset)
    {
        echo __METHOD__, "\n";    
    }

    public function offsetSet ($offset, $value)
    {
        echo __METHOD__, "\n";    
    }

    public function offsetUnset ($offset)
    {
        echo __METHOD__, "\n";
    }

}

$object = new Foo;
$object['foo'] = 'bar';

This program defines the class Foo and implements each of the ArrayAccess interface’s abstract methods. Then, it instantiates a Foo object, and tries to set an array property. We’ve also programed each method to output its name whenever the program calls the method.

If you run the above program, you’ll see output something like this

Foo::offsetSet

Now try getting a property.

$object = new Foo;
$object['foo'] = 'bar';
$bar = $object['foo'];

echo "The value we fetched: " . $bar,"\n";

The above will output something like the following

Foo::offsetSet
Foo::offsetGet
The value we fetched: 

What did this teach us? When we tried to set a value via array access ($object['foo'] = 'bar';), behind the scenes PHP called our class’s offsetSet method. When we tried to get a value via array access ($bar = $object['foo'];), behind the scenes PHP called offsetGet. However, no value was actually set or gotten from the object (The value we fetched:).

That’s how the PHP ArrayAccess interface works. It has some behind the scenes magic that will call a method on your class when you perform an array operation on your object, but it’s still up to you to implement that access. If you try something like this

<?php
class Foo implements ArrayAccess
{
    protected $_data=array();
    public function offsetExists ($offset)
    {
        return array_key_exists($offset, $this->_data);
    }

    public function offsetGet ($offset)
    {
        return $this->_data[$offset];
    }

    public function offsetSet ($offset, $value)
    {
        $this->_data[$offset] = $value;
    }

    public function offsetUnset ($offset)
    {
        unset($this->_data[$offset]);
    }

}

$object = new Foo;
$object['foo'] = 'bar';
$bar = $object['foo'];

echo "The value we fetched: " . $bar,"\n";

You’ll have much better results.

The value we fetched: bar

While you initial reaction may be annoyance — “Why doesn’t PHP just implement this for me” — by giving you the responsibility of implementing your own ArrayAccess rules, the PHP core developers have given you (or the framework developers you rely on) tremendous power to create new behavior for your own objects.

Originally published September 15, 2014