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 make
s 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.