Categories


Archives


Recent Posts


Categories


Binding Objects as Laravel Services

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.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

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
Series Navigation<< PHP ArrayAccessPHP Magic Methods and Class Aliases >>

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 21st September 2014

email hidden; JavaScript is required