Categories


Archives


Recent Posts


Categories


Laravel Objects

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!

Laravel is already a well documented system. The quick-start guide guide has all the information a developer needs to start building applications with Laravel. What’s less well documented, and more interesting to me, is documentation of Laravel’s implementation. The PHP community has a pretty good handle on how to use MVC systems, but less attention is paid to the code that makes these systems run.

Today we’re going to cover the basics of Laravel’s global application object, and how to instantiate “Laravel objects” like an internals developer. A lot of this code will end up being the sort of thing you’d never write day-to-day with Laravel, but understanding the internals will help you debug core system code, understand module implementations, and be a better overall PHP developer ready to handle whatever system eventually replaces Laravel.

Getting Started

The first thing we’ll want to do is create a simple route where we’ll put our code. Open up app/routes.php and add the following to the bottom of the file.

#File app/routes.php
Route::get('tutorial', function(){        
    return '<p>Done</p>';
});

Next, load the following URL in your browser.

http://laravel.example.com/tutorial

You should see the text Done. Next, replace your code with the following.

#File app/routes.php
Route::get('tutorial', function(){        
    $app = app();
    var_dump(get_class($app));
});

If you reload the page, you should see the following text

string 'Illuminate\Foundation\Application' (length=33)

The global app() function returns the Laravel application object. There are other ways to access the Application object, but for now we’re going to keep things simple and use the app() function.

For the remainder of this article we’ll put all our code in this tutorial route.

Laravel Objects

A Laravel object is just a PHP object, but a PHP object that’s been instantiated via the Application object’s make method. You could also call the make method the make factory, or the make factory method.

If you’re more a learn by doing person, consider the following. In plain old PHP, you can instantiate a simple object with code like this

#File: app/routes.php
Route::get('tutorial', function(){       
    $object = new stdClass;
    $object->foo = 'bar';

    var_dump($object);    
});

In Laravel, you instantiate an object with code that’s slightly different.

#File: app/routes.php
Route::get('tutorial', function(){       
    $app = app();
    $laravel_object = $app->make('stdClass');
    $laravel_object->foo = 'bar';
    var_dump($laravel_object);        
});

That is, you call the Illuminate\Foundation\Application object’s make method, and pass in the name of the class. In both cases, the above code will output something like this

object(stdClass)[34]
  public 'foo' => string 'bar' (length=3)

You may be thinking something like

Why bother with the Laravel make method if the results are the same?

We’ll cover that more below — this is one of those catch 22’s in programming where it’s necessary to get a little experience using the system before you can understand why you’d want to use the system.

The make method will work with any PHP class. For example, if you create a simple Laravel model class with the following contents in app/models/Hello.php

#File: app/models/Hello.php
<?php
class Hello
{
}

You can instantiate it via PHP with the following

#File: app/routes.php
Route::get('tutorial', function(){       
    $object = new Hello;
    var_dump($object);        
});

To create a Laravel object using the Hello class, you’d do the following.

#File: app/routes.php
Route::get('tutorial', function(){       
    $app = app();
    $hello = $app->make('Hello');
    var_dump($hello);        
});

Here we’ve used make to instantiate the class Hello. This will work with any PHP class that Laravel can autoload. For example, if we wanted to instantiate the Illuminate/Hashing/BcryptHasher class, we’d do it like this

#File: app/routes.php
Route::get('tutorial', function(){     
    $app = app();    
    $bcrypt = $app->make('Illuminate\Hashing\BcryptHasher');
    var_dump($bcrypt);
});

An important warning here — make does not inherit the scope of the PHP file where you call make — you’ll need to use the full namespace path.

The make factory also supports constructor parameters. For example, consider the following Hello class

#File: app/models/Hello.php
<?php
class Hello
{
    public function __construct($one='default1', $two='default2')
    {
        echo "First  Param: $one","\n<br>\n";
        echo "Second Param: $two","\n<br>\n";
        echo "\n<br>\n";
    }
}

Here we’ve added a __construct method to our Hello class. If you’re super new to PHP, PHP calls a class’s __construct method whenever you instantiate an object. If we ran the following code,

#File: app/routes.php
Route::get('tutorial', function(){       
    $o = new Hello;

    $o = new Hello('changed1','changed1');
});

We’ll get output something like this

First Param: default1 
Second Param: default2 

First Param: changed1 
Second Param: changed1 

That is, instantiating the object without parameters results in the class using the default values for the constructor parameters. If we use parameters when instantiating our object, the class uses the parameters.

So, now consider the same scenario with make. The following code

#File: app/routes.php
Route::get('tutorial', function(){       
    $app = app();
    $app->make('Hello');        
});

will output

First Param: default1 
Second Param: default2 

So that’s the default values used — but how do we tell Laravel we want to use constructor parameters? It’s not obvious, but it’s easy to remember once you’ve learned it — just use make‘s second parameter. The following

#File: app/routes.php
Route::get('tutorial', function(){       
    $app = app();
    $app->make('Hello', array('laravel1','laravel2'));
});

will output

First Param: laravel1 
Second Param: laravel2 

That is, Laravel’s second argument accepts an array of parameters to pass to the constructor. Since Laravel is PHP 5.4+ only, we could also write that as

$app->make('Hello', ['laravel1','laravel2']);

Here we’re using the new-in-5.4 [] language construct to create an array. The [] syntax may seem like a small thing, but it’s a good tool for improving the readability of code that uses a lot of arrays.

Why use Laravel Objects?

So that’s the basics of Laravel object instantiation. The question that’s sure to be bouncing around the back of your head is — “Why replace PHP’s new keyword with a factory method if the objects are the same”?

The benefit of using a factory method to instantiate an object is that you give yourself the ability to monitor, control, and change how objects are instantiated in your PHP system. The behavior of PHP’s objects are hardwired into the language. You create an object, the constructor’s called, etc. There’s no chance to change how any of that works.

If you’re a low level C/C++ programmer you could probably write a PHP module to change the behavior of PHP’s object instantiation, but that’s a order of magnitude more work to get right, and low level C/C++ wizards are hard to come by.

By creating a system to replace PHP’s instantiating of objects, the Laravel systems programmers have given themselves the ability to change the behavior of object instantiation, and give their objects super powers. The only caveat is they need to convince you, the PHP client programmer, to use their factory pattern to instantiate objects.

We’ll talk more about that convincing in a future article, but you’re probably more interested in the super powers we mentioned. What sort of super powers do Laravel objects have? Many of them are related to the Application object’s “container and binding” features, which we’ll also cover in a future article. There are, however, a few super powers we can talk about today.

Class Aliasing

Laravel’s implemented a class aliasing feature for their objects. Other names for this functionality include duck-typing, monkey patching, or inversion of control. If you’re a Magento programmer, you’re used to calling it class rewrites. If you’re a Drupal developer the concept is “Pluggable”.

Regardless of what you call it, Laravel allows you to swap out class definitions at runtime. For example, consider the following code from earlier.

#File: app/models/Hello.php
<?php
class Hello
{
}

#File: app/routes.php
Route::get('tutorial', function(){       
    $app = app();
    $hello = $app->make('Hello');
    var_dump($hello);        
});

Here we instantiated an object Hello. Let’s say we want to replace Hello with a new object, Welcome. First we’ll need to create our new class. To do so, add the following code to the app/models/Welcome.php file.

#File: app/models/Welcome.php
<?php
class Welcome extends Hello
{
}    

Here we’ve defined a new class Welcome, and had it extend our original class Hello. This means Welcome will behave exactly the same as Hello.
Then, at the end of Laravel’s app/start/global.php file, add the following code. (If you’re not familiar with global.php, read up on the Laravel request lifecycle)

#File: app/start/global.php
$app = app();
$alias = $app->alias('Welcome', 'Hello');

Here we’ve used the Application object’s alias method to tell Laravel whenever it instantiates a Hello object, it should use the Welcome class. Try running the following code

#File: app/routes.php
Route::get('tutorial', function(){       
    $app = app();
    $hello = $app->make('Hello');
    var_dump($hello);        
});

And you’ll see PHP will dump a Welcome object instead of a Hello object.

object(Welcome)[141]

Once an alias is setup, we can start adding new methods to the Welcome class, extending the system’s functionality without needing to replace the original Hello class. This is a powerful feature, and if you’ve ever tried setting up a class rewrite in a system like Magento, you can see the immediate advantage of Laravel’s simpler syntax.

Instantiation Events

Another advantage of using make is the Application object’s instantiation events, or as Laravel calls them — “resolving callbacks”. That is, a PHP callback which fires whenever an object is made. (i.e. when resolving the object)

Try adding the following to the end of you app/start/global.php file

#File: app/start/global.php
$app = app();
$app->resolving('Welcome', function($object, $app){
    echo "I just instantiated a " . get_class($object) . "\n<br>\n";
});

and then reload the page from above. You should see the following content in your browser.

I just instantiated a Welcome

What we did here was tell Laravel that anytime a Welcome object is instantiated, we should call the anonymous function passed in as the second argument to resolving

function($object, $app){
    echo "I just instantiated a " . get_class($object) . "\n<br>\n";
}

Laravel makes heavy use f anonymous functions as callbacks. If you’re not familiar with anonymous functions in programming, this old Joel on Software article is still the best primer I’ve seen on the subject.

Using the resolving feature, we can track when Laravel instantiates certain objects, and also change the object that’s passed into the callback. This powerful feature enables to you do anything you’d like to all objects of a certain type without needing to modify a single line of existing code.

The application also has a “global” resolving callback. This allows you to specify a callback/anonymous function for Laravel to call whenever it makes an object. If you want to give it a try, replace the above code in app/start/global.php with the following

#File: app/start/global.php
$app = app();
$app->resolvingAny(function($object, $application){
    //we'll explain this conditional in a minute. 
    if(!is_object($object))
    {
        return;
    }
    echo "I just instantiated a " . get_class($object) . " object \n<br>\n";
});

Reload your page, and you should see something like the following.

I just instantiated a Illuminate\Routing\Router object 
I just instantiated a Welcome object 

Laravel called the resolvingAny callback/listener you specified when it instantiated a Illuminate\Routing\Router object, as well as our Welcome object. If we had set this callback up earlier in the bootstrap process we would have seen more of Laravel’s internal objects. Laravel will send anything created through make to the resolvingAny callback.

This brings us to our final point for today. You’re probably wondering about that conditional guard clause.

if(!is_object($object))
{
    return;
}

Let’s replace that the with some more debugging code

#File: app/start/global.php
$app = app();
$app->resolvingAny(function($object, $application){
    //we'll explain this conditional in a minute. 
    if(!is_object($object))
    {
        echo "What's inside \$object: [" , $object, "]\n<br>\n";
    }
});

If you reload with the above in place, you’ll see something like the following

What's inside $object: [local] 

For some reason the resolvingAny method has intercepted something that’s not an object. Instead of an object, the parameter contains the string local. Why has a method meant to listen for instantiated objects received a string?

That’s a larger question we’ll start answering next time. Today we’ve concentrated on the object instantiating features of the Application object, but that’s only one small part of the Application object’s responsibilities in Laravel. Next time we’ll cover the Application object’s container features, what binding objects into the container means, and why a method meant for objects might sometimes receive a string.

Originally published September 10, 2014
Series NavigationPHP ArrayAccess >>

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 10th September 2014