Eloquent ORM Static Meta-Programming

So far in this series we’ve stayed pretty focused on core Laravel concepts and their underlying implementation. However, this week we’re going to go slightly farther afield and discuss some of the static method meta-programming used by Laravel’s “Eloquent” ORM.

What’s an ORM

ORM stands for Object Relational Mapper. In less fancy terms, ORMs are systems that hide the underlying storage technology for your application’s data, and present client-programmers with simpler objects to manipulate.

As a web developer, the most common ORM pattern you’ll see is ActiveRecord. Most frameworks that ship with an ORM use one that implements and/or is inspired by ActiveRecord, and Laravel’s Eloquent ORM is no exception.

This article isn’t a full ORM, ActiveRecord, or Eloquent tutorial. If you’re not familiar with the concepts you’ll probably be OK — just think of an ORM as that thing we do instead of writing raw SQL.

Eloquent Parts

First, here’s a lightning round primer on the trinity of objects that you’ll use most commonly with Eloquent.

First, there’s your standard model object. A model represents the data for a single object or item. All models in Laravel inherit from the Illuminate\Database\Eloquent\Model object. There’s also an Eloquent class_alias setup in a stock Laravel system

#File: app/config/app.php
'aliases' => array(
    //...
    'Eloquent'        => 'Illuminate\Database\Eloquent\Model',    

This allows users to use the global shortcut Eloquent when defining their models.

#File: app/models/SportsBallPlayer.php
class SportsBallPlayer extends Eloquent
{
}

This is another area where Laravel shields users from PHP’s underlying namespace system.

To fetch models, Laravel has a query builder object (class name: Illuminate\Database\Query\Builder). You use a query builder object to fetch specific models from your system. In pseudo-code that might look like this

//pseduo code to simplify things, we'll explain more below
$player = new SportsBallPlayer;
$query_builder = new \Illuminate\Database\Query\Builder;
$query_builder->setModel($player);
$results = $query_builder
    ->where('height_in_inches','>','72')
    ->get();

You’re probably wondering what’s in the $results variable above. That brings us to the third, and final Laravel ORM object we’ll talk about today: The collection object (class name: Illuminate\Database\Eloquent\Collection). The collection object is an array-like PHP object that contains a collection of models returned by the query builder. The most common way you’ll use this object is via a for each statement

foreach($results as $model)
{
    var_dump($model->toArray());
}

If you’re new to PHP you may be surprised that data structures other than the built-in array type can be for eached. While it’s beyond the scope of this article, this ability comes by way of the IteratorAggregate interface. Magento developers should be familiar with the concept, but take note that Laravel doesn’t have typed collections.

The __call and __callStatic Methods

With that quick primer out of the way, we’re set to explore Eloquent’s use of __callStatic. That is, using our model above, what happens if we say

SportsBallPlayer::callTheThing(...);

and the SportsBallPlayer class doesn’t have a static callTheThing method defined. If you’ve been following along, you’ll know we want to jump right to the base model class’s __callStatic method

#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
public static function __callStatic($method, $parameters)
{
    $instance = new static;

    return call_user_func_array(array($instance, $method), $parameters);
}

As __callStatic methods go, this is pretty simple. Remember, the static keyword, as used here, is how a static method can refer to its calling class. In our case, the following lines are equivalent

$instance = new static;
$instance = new SportsBallPlayer

This means calling an undefined static method on an Eloquent model will

  1. Instantiate a new instance of that model
  2. Pass the static call on as an instance method call

Put in code, that means this

SportsBallPlayer::all(...);

is equal to this

$model = new SportsBallPlayer;
$model->all(...);

That’s pretty simple, and a clever way to save developers from needing to instantiate a model when they want to work with it.

However, let’s go back to our original example

SportsBallPlayer::callTheThing(...);

//or

$model = new SportsBallPlayer;
$model->callTheThing(...);

There’s no instance method named callTheThing. As you’ve no doubt already guessed, Eloquent models have a __call method defined as well, which will catch calls to undefined methods

#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php    
public function __call($method, $parameters)
{
    if (in_array($method, array('increment', 'decrement')))
    {
        return call_user_func_array(array($this, $method), $parameters);
    }

    $query = $this->newQuery();

    return call_user_func_array(array($query, $method), $parameters);
}

We’re going to ignore that first conditional, and concentrate on the last two lines

#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php    
$query = $this->newQuery();

return call_user_func_array(array($query, $method), $parameters);

When you call an undefined method on an Eloquent model, Laravel will pass that method call on to an automatically instantiated query builder object. The newQuery method above returns a prepared query builder object, ready to query for your model. So, in simplified terms, that means our example

SportsBallPlayer::callTheThing(...);

//or

$model = new SportsBallPlayer;
$model->callTheThing(...);

actually expands out into something like

$model = new SportsBallPlayer;
$query = new Illuminate\Database\Query\Builder;
$query->setModel($model);
$query->callTheThing(...);

We say “simplified” because the instantiation of a query builder object is a complicated thing. It’s beyond the scope of this article, but if you’re interested it’s worth tracing out exactly what the newQuery method does.

Why do this?

In our callTheThing example, the end result here is the same. Since the query builder object doesn’t have a callTheTing method defined, PHP dies with a fatal error. That said, let’s consider a more common example.

SportsBallPlayer::where('height_in_inches','>','72')

There’s no where method defined on an Eloquent model. Instead, by passing the undefined method calls onto the query builder object, Laravel provides an instant shortcut to querying for a specific type of object. Even better, most of the query builder methods return themselves, which means method chaining is possible

SportsBallPlayer::where('height_in_inches','>',72)
->where('that_coach','=','Quite a Character')
->where('scoredowns', '=', 10);

Through a clever bit of meta-programming, Laravel maintains separation between the model logic and the querying logic, while still offering a simplified format that any PHP programmer can get started with. I know some programmers chafe at the marketing angle in many of Laravel’s system names, but this really is an eloquent pattern compared to many other ORMs.

Tradeoffs

There are of course, tradeoffs. The first I want to talk about is the similarity between these static method calls and Laravel facade calls. Consider the following

Auth::isLoggedIn();

Is this calling isLoggedIn on an Auth facade? Or is there an Eloquent model named Auth with an isLoggedIn method? Or (not likely, but still possible), does the query builder object have an isLoggedIn method for some reason? I know it’s a common refrain in this series, but Eloquent’s use of the static method calling surface area to implement meta-programming features means code is less readable until you have some level of expertise in the system. It’s one more thing you need to check when you’re debugging someone else’s code.

There’s also some confusion that’s confined to Eloquent. Consider the following

$model_first = SportsBallPlayer::whereRaw('1=1')->first();
$model_first = SportsBallPlayer::all()->first();

These two calls look the same, don’t they? In the simple case, they are. However, consider this

$model_first = SportsBallPlayer::whereRaw('1=1')->first(['name']);
var_dump(
    $model_first->toArray()
);

$model_second = SportsBallPlayer::all()->first(['name']);
var_dump(
    $model_second->toArray()
);

Again, identical looking calls. The first works as expected, restricting the columns requested from the database

array (size=1)
  'name' => string 'Gibson' (length=6)

However, the second call ends up throwing an exception

Argument 1 passed to Illuminate\Support\Collection::first() must be an instance of Closure, array given, called in /path/to/laravel/app/routes.php on line 257 and defined …

What gives? If we apply what we’ve learned, we know Eloquent routes the call to whereRaw through a query builder object. The query builder object implements a return $this for method chaining

#File: framework/src/Illuminate/Database/Query/Builder.php    
public function whereRaw($sql, array $bindings = array(), $boolean = 'and')
{
    //...
    return $this;
}

Which means the call to first is also made on the query builder object

#File: framework/src/Illuminate/Database/Query/Builder.php   
public function first($columns = array('*'))
{
    $results = $this->take(1)->get($columns);

    return count($results) > 0 ? reset($results) : null;
}

If we consider the second call, it’d be easy to jump to the same conclusion

$model = SportsBallPlayer::all()->first();

However, Laravel does not route the call to all through a query builder object. Why not? Because an all method is actually defined on the base Eloquent model

#File: framework/src/Illuminate/Database/Eloquent/Model.php
public static function all($columns = array('*'))
{
    $instance = new static;

    return $instance->newQuery()->get($columns);
}

This means all returns a collection object, and it is the collection object’s first method that’s really called.

#File: framework/src/Illuminate/Support/Collection.php
public function first(Closure $callback = null, $default = null)
{
    if (is_null($callback))
    {
        return count($this->items) > 0 ? reset($this->items) : null;
    }
    else
    {
        return array_first($this->items, $callback, $default);
    }
}

The problem here is two fold — first, without intimate knowledge of the base classes it’s impossible to tell which methods are passed along to a query builder object, and which are not. The second aspect is both the collection object and the query builder object have identical method names. This can lead to a number of confusing scenarios when querying for Eloquent models, with your only solution being rote memorization or relying on third party extensions for your IDE’s auto-complete.

These are, of course, the perils of meta-programming. The Eloquent querying model is an improvement — but at the cost of some confusion and learning curve. We’ll have more to say about this next time in a our final wrap-up article for this series.

Originally published November 15, 2014

Laravel Service Manager Indirection

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.

Consider the Laravel caching facade. Laravel lets you store and retrieve values to/from the cache with the following syntax

Cache::put('key','value');
Cache::get('key');

If you’ve been following along, you know there’s no global class named Cache. Cache is an alias that points to the Facade class Illuminate\Support\Facades\Cache. This Facade class defines a service accessor/identifier: cache

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php
protected static function getFacadeAccessor() { 
    return 'cache'; 
}

In a default Laravel installation, the service accessor cache points to the class Illuminate/Cache/CacheManager, bound in the register method of the CacheServiceProvider

#File: vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php
public function register()
{
    $this->app->bindShared('cache', function($app)
    {
        return new CacheManager($app);
    });

    //...
}

You’ll notice Laravel binds cache as a shared service, meaning there’s only ever one instance of the Illuminate\Cache\CacheManager object.

So, as a developer familiar with facades, you know if you wanted to look for the definitions of the put and get methods

Cache::put('key','value');
Cache::get('key');

that you’d want to look at Illuminate\Cache\CacheManager.

However, if we look at this class definition, of the 14 methods defined on the CacheManager class, there’s not a put or get among them.

#File: vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php
class CacheManager extends Manager 
{
    protected function createApcDriver() //...
    protected function createArrayDriver() //...
    protected function createDatabaseDriver() //...
    protected function createFileDriver() //...
    protected function createMemcachedDriver() //...
    protected function createRedisDriver() //...
    protected function createWincacheDriver() //...
    protected function createXcacheDriver() //...
    protected function getDatabaseConnection() //...
    protected function repository(StoreInterface $store) //...
    public function getDefaultDriver() //...
    public function getPrefix() //...
    public function setDefaultDriver($name) //...
    public function setPrefix($name) //...
}

At this point, if you’re an inexperienced developer, or even an experienced developer new to Laravel, you may think you’ve followed the facade indirection chain incorrectly — that the Cache facade actually locates a different service, or maybe something’s extended Laravel to make the service identifier cache point at something else.

While an understandable conclusion, that’s not what’s going on here. The Illuminate\Cache\CacheManager class is the cache service, and the Cache facade does point at the cache service. What’s actually happening is another level of indirection.

Who you Gonna __call

The piece we’re missing is the cache manager’s parent class has a __call method. That is, expanded out, the call

Cache::get(...);

Looks like this

$manager = app()->make('cache');
/* @var Illuminate\Cache\CacheManager */
$manager->get();

When PHP can’t find a get method defined on the Illuminate\Cache\CacheManager class or any of its parents, PHP invokes the __call method, defined in the CacheManger’s parent Illuminate\Support\Manager class

#File: login/vendor/laravel/framework/src/Illuminate/Support/Manager.php
public function __call($method, $parameters)
{
    return call_user_func_array(array($this->driver(), $method), $parameters);
}

The Manager’s __call method fetches an object with a call to $this->driver(), and then passes the get call to this object. For the specific case of Cache::get('key'), that looks like this

#File: login/vendor/laravel/framework/src/Illuminate/Support/Manager.php
public function __call('get', $parameters)
{
    $driver = $this->getDriver();
    return call_user_func_array(
        [$driver, 'get'],
        $parameters);
}

While this may seem needlessly confusing, this indirection is what allows Laravel to support multiple cache engines. In other words, it’s why you can store cache values in Memcached, Redis, a database, the filesystem, etc.

Where things start to get interesting, and more confusing, is in the driver method. We’re going to follow the method invokation all the way down — if it gets a little confusing don’t worry, we’ll sum it up at the end for you.

#File: login/vendor/laravel/framework/src/Illuminate/Support/Manager.php
public function driver($driver = null)
{
    $driver = $driver ?: $this->getDefaultDriver();

    // If the given driver has not been created before, we will create the instances
    // here and cache it so we can return it next time very quickly. If there is
    // already a driver created by this name, we'll just return that instance.
    if ( ! isset($this->drivers[$driver]))
    {
        $this->drivers[$driver] = $this->createDriver($driver);
    }

    return $this->drivers[$driver];
}

The driver method

  1. Fetches a driver identifier with getDefaultDriver
  2. Passes this string to the createDriver method, which returns an object
  3. Returns that object (caching a copy in ->drivers[])

So, if we investigate the getDefaultDriver method, we’ll find it’s an abstract method

#File: login/vendor/laravel/framework/src/Illuminate/Support/Manager.php
abstract public function getDefaultDriver();

This means it’s the responsibility of each individual service manager class to return a driver. Let’s take a look at the cache manger’s implementation

#File: vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php
public function getDefaultDriver()
{
    return $this->app['config']['cache.driver'];
}

We can see the cache manager references the Laravel configuration to find the cache driver. In a stock system this is the string file

#File: app/config/cache.php
return array(
    'driver' => 'file',

So, returning to the driver method, we can see the object instantiation call looks like this

#File: login/vendor/laravel/framework/src/Illuminate/Support/Manager.php
public function driver($driver = null)
{
    //...
        $this->drivers['file'] = $this->createDriver('file');
    //...
    return $this->drivers['file'];
}

That is, driver calls the createDriver method. If we take a look at an edited version of the createDriver method

#File: login/vendor/laravel/framework/src/Illuminate/Support/Manager.php
protected function createDriver($driver)
{
    //...
    $method = 'create'.ucfirst($driver).'Driver';
    //...    
        return $this->$method();
    //...

    throw new \InvalidArgumentException("Driver [$driver] not supported.");
}

We see the createDriver method uses the driver name (file) to create a method name

createFileDriver

and then calls that method. Similar to the earlier getDefaultDriver abstract method, it’s the responsibility of the parent class to implement a createFileDriver method. Unlike the previous method, there’s no abstract createFileDriver method.

If we take a look at the definition of of createFileDriver, we’ll see two additional method calls (createFileDriver, repository)

#File: vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php
protected function createFileDriver()
{
    $path = $this->app['config']['cache.path'];

    return $this->repository(new FileStore($this->app['files'], $path));
}

protected function repository(StoreInterface $store)
{
    return new Repository($store);
}

That eventually results in the instantiation of a Illuminate\Support\Manager\Filestore object, and the return of an Illuminate\Support\Manager\Repository object. Getting into the full implementation of the Cache service is beyond the scope of this article, but your main takeaway should be realizing when you call Cache::get, the actual method you’re calling is Illuminate\Support\Manager\Repository::get(...)

#File: vendor/laravel/framework/src/Illuminate/Cache/Repository.php
public function get($key, $default = null)
{
    $value = $this->store->get($key);

    return ! is_null($value) ? $value : value($default);
}

As you can see above, the Repository::get(...) method passes the call onto the store property. The store property contains the instantiated Illuminate\Support\Manager\FileStore.

Indirection All the Way Down

So — that’s a pretty twisty path. Here’s a directed graph that outlines it more tersely

That’s five levels of indirection from a client programmer calling get, to the actual function that does the work of get executing. The Cache and Auth facades also point to services that are implemented with the Illuminate\Support\Manager pattern.

This sort of thing interests me for a lot of reasons. The one I’ll talk about today is that, Laravel, under the covers, is arguably more complicated than systems like Magento, Zend, and Symfony. Magento’s factory pattern

Mage::getModel('catalog/product');

is one or two levels of indirection. You could argue that Magento’s layout system is maybe two or three levels — but nowhere near Laravel’s five. So why is Laravel embraced by so many when a “less complicated” system like Magento receives scorn for its over-complications?

It’s because you don’t need to understand Laravel’s complicated nature to get things done. On the surface level, when most developers call something like

Cache::get(...)
Student::find($id);

they’re not thinking of the underlying software patterns, and Laravel doesn’t force them to. The basics in Laravel are made super simple. For a programmer to get started in Laravel all they need to do is drop some code in app/routes.php and they have a responding page. To get started in Magento, Zend, Symfony, any many other frameworks, you need to undertake a multiple step process that involves editing configuration files and/or naming controller files in specific ways.

Under the hood Laravel’s routing system is just as complicated as any MVC framework’s routing system. It’s just that end-user programmers don’t need to dive into that level of detail to get their job done.

One of Laravel’s core values, whether stated by its maintainers or not, is to put the power of modern framework driven programming in the hands of people who might not otherwise understand modern framework driven programming.

The Tradeoff

While this approach has led to massive success and buzz for Laravel, it does create some problems when the framework doesn’t do what a user expects. For example, it’s very common when debugging that you need to dive deeply into framework code to make sure something is doing what you think it is. Most software bugs come down to a mistaken assumption, and the quickest way to undo that assumption is watch the underlying framework code do its job.

Unfortunately, when you see

Cache::get(...)

you have no idea what the underlying class is without going through the above framework debugging. While the Auth and Session facades follow the same Illuminate\Support\Manager pattern, other stock facades implement one off __call logic, which means anytime you want to dig into core service code you need to stop, break flow, figure out where the final implementation class lives, and then return to work.

While not a mortal sin, or even a venial one, this is something you’ll need to deal with if you’ll be building long term operational systems with Laravel. While one could argues that the Laravel core is solid enough not to require regular debugging, the ecosystem of third party Laravel code contains many popular but less well programmed packages/modules.

This indirection can also lead to confusion with the other Laravel systems that use a static calling syntax (::), but are not considered facades. Next time we’ll explore this further when we look at the static call related features of the Eloquent ORM.

Originally published November 9, 2014

Laravel’s MacroableTrait

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.

Another quick primer this week, but this time it’s Laravel specific.

The idea of a “macro” has a long history in computer science and programming. This long history means it’s one of those words with numerous different overloaded meanings. The first time I encountered something called a macro it was in mid-to-late-1990s visual basic/vb-script, where a macro is a function (or subroutine) with no parameters. The C programming language has macros, but in C they’re a small programming language within a programming language that lets you change the contents of your program before sending it to the compiler. Even Microsoft Office has macros, which are small recordable programs that allow you to automate tasks.

While each of these things are different, they all share the common theme of performing a programmatic task, but with limited access to the full features of the real programming environment.

With that context, we’re going to take a look at Laravel’s implementation of the macro concept.

What Does “Macroable” Mean in Laravel

To start, let’s add the following to our app/routes.php file.

#File: app/routes.php
class Hello{}

Route::get('testbed', function(){

    $hello = new Hello;

    $hello->sayHi();

});

In real life we’d never define a class in the app/routes.php file, but it’s convenient for demo/learning purposes.

If we load the testbed route in a browser

http://laravel.example.com/testbed

We’ll get the following PHP error

Call to undefined method Hello::sayHi()

This is unsurprising, as we never defined a sayHi method for the Hello class.

Next, let’s add the MacroableTrait to our class

#File: app/routes.php   
class Hello
{
    use Illuminate\Support\Traits\MacroableTrait;
}

Here we’ve used the full trait name, namespace and all. If you’re not familiar with traits, checkout last week’s primer. Traits follow the same namespace rules as classes, and using a trait will invoke the PHP autoloader. If we load the page with the above in place, you’ll still see the same error

Call to undefined method Hello::sayHi()

However, now try adding the following code to your route

#File: app/routes.php    
class Hello
{
    use Illuminate\Support\Traits\MacroableTrait;
}
Route::get('testbed', function(){
    // include '/tmp/test.php';

    $hello = new Hello;
    Hello::macro('sayHi', function(){
        echo "Hello","\n<br>\n";
    });
    $hello->sayHi();
    Hello::sayHi();
});

Here we’ve called the static method macro on Hello. While Hello doesn’t define a static method named macro, it does inherit one from the Illuminate\Support\Traits\MacroableTrait trait.

If you reload the page with the above in place, you’ll see the following.

Hello 
Hello 

This is what the MacroableTrait does — it allows you to add a method (or, a “macro”) to a PHP class programmatically. By calling the macro method, we’ve effectively added the sayHi method to our Hello class, and all objects instantiated from that class. The above example is a little silly (again, for pedagogical reasons) — in real life you’ll see the MacroableTrait used in some global bootstrap-ish code to make a method available to other programmers. An example you might be familiar with from Laravel 4.2 is the Form helper macros.

How MacroableTrait Works

The MacroableTrait is simpler than you might think. You can find its definition in the following file

vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php

The most obvious place to start your investigation is the macro method we called earlier

#File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php
public static function macro($name, callable $macro)
{
    static::$macros[$name] = $macro;
}

This method stashed the anonymous PHP function in a static array property, indexed by $name. If you scroll down a bit in the file, you’ll see our old friends __call and __callStatic. The __call method

#File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php
public function __call($method, $parameters)
{
    return static::__callStatic($method, $parameters);
}

Simply passes the method call on to the __callStatic method. The __callStatic method

#File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php    
public static function __callStatic($method, $parameters)
{
    if (static::hasMacro($method))
    {
        return call_user_func_array(static::$macros[$method], $parameters);
    }

    throw new \BadMethodCallException("Method {$method} does not exist.");
}

will fetch the anonymous function or PHP callback added with the call to macro, and then call the anonymous function/callback using PHP’s call_use_func_array method.

If you didn’t follow that, let’s trace things again, this time with our example code. First, when we called

#File: app/routes.php      
Route::get('testbed', function(){    
    //...
    Hello::macro('sayHi', function(){
        echo "Hello","\n<br>\n";
    });
    //...
});    

we were telling our class to store the anonymous function inside of static::$macros['sayHi'].

Next, here’s where we called sayHi (both statically and via an instance method)

#File: app/routes.php       
Route::get('testbed', function(){        
    //...
    $hello->sayHi();
    Hello::sayHi();
    //...
});    

Since sayHi isn’t defined on the class Hello, PHP ends up calling __call (for the -> invokation) and __callStatic (for the :: invocation), per the rules of PHP Magic Methods. The __call method simply passes the method call onto __callStatic. This means __callStatic actually executes for both instance and static method calls.

If we put on some x-ray variable specs, the method call looks something like this

#File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php        
public static function __callStatic('sayHi', $parameters)
{
    if (static::hasMacro('sayHi'))
    {
        return call_user_func_array(static::$macros['sayHi'], $parameters);
    }

    throw new \BadMethodCallException("Method {'sayHi'} does not exist.");
}

Or, expanding that out even further, like this

#File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php    
public static function __callStatic('sayHi', $parameters)
{
    if (static::hasMacro('sayHi'))
    {
        return call_user_func_array(function(){
            echo "Hello","\n<br>\n";
        }, $parameters);
    }

    throw new \BadMethodCallException("Method {'sayHi'} does not exist.");
}    

By leveraging PHP’s magic methods and static functions, the MacroableTrait trait gives you the ability to dynamically add methods to objects at runtime — a feature usually reserved for “more advanced” dynamic languages like ruby/python.

Gotchas

While powerful, keep in mind macros (by design) have no knowledge of the other properties and methods of a class/object. Since you’re using an anonymous function (or PHP callback) to add your method, this function/callback won’t have access to the usual variables like $this, self, or static. This means the methods you add via a MacroableTrait will never be more than stateless helper methods. If you need access to an object or class’s state, then a macro is the wrong tool for your job.

The other thing you should keep in mind when considering the MacroableTrait is whether you want to add another item to the already busy “static method namespace” in your Laravel program. We’ll talk more about this next time, when we dive deeper into the various different things a static method call might do in Laravel.

Originally published October 26, 2014