Service Container

Introduction

The service container can be used exactly the same as described in Laravel documentation.

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.

Let's look at a simple example:

<?php namespace Anomaly\UsersModule\Http\Controller;

use Anomaly\Streams\Platform\Http\Controller\PublicController;
use Anomaly\UsersModule\User\Contract\UserRepositoryInterface;

class UsersController extends PublicController
{

    public function view(UserRepositoryInterface $users)
    {
        if (!$user = $users->findByUsername($this->route->getParameter('username'))) {
            abort(404);
        }

        return $this->view->make('anomaly.module.users::users/view', compact('user'));
    }
}

In this example, the UserController needs to retrieve a specific user from a data source. So, we will inject a service that is able to retrieve users. In this context, our UserRepositoryInterface to retrieve user information from the database. However, since the repository interface is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the UserRepository when testing our application.

A deep understanding of the service container is essential to building a large powerful application.

Bindings

This section will go over how to define a bindings in the service container.

Almost all of your service container bindings in PyroCMS will be registered within addon service providers, so most of these examples will demonstrate binding in that context.

For more information on registering bindings with native Laravel service providers please refer to Laravel documentation on Service Container > Binding.

The Bindings Property

Within an addon service provider you can register a binding by defining them in the $bindings array. Bindings should be defined in an Abstract => Instance format:

protected $bindings = [
    'login' => 'Anomaly\UsersModule\User\Login\LoginFormBuilder',
    'register' => 'Anomaly\UsersModule\User\Register\RegisterFormBuilder',
    'reset_password' => 'Anomaly\UsersModule\User\Password\ResetPasswordFormBuilder',
    'forgot_password' => 'Anomaly\UsersModule\User\Password\ForgotPasswordFormBuilder',
    'Illuminate\Auth\Middleware\Authenticate' => 'Anomaly\UsersModule\Http\Middleware\Authenticate',
    'Anomaly\Streams\Platform\Model\Users\UsersUsersEntryModel' => 'Anomaly\UsersModule\User\UserModel',
    'Anomaly\Streams\Platform\Model\Users\UsersRolesEntryModel' => 'Anomaly\UsersModule\Role\RoleModel',
    'Anomaly\Streams\Platform\Http\Middleware\Authenticate' => 'Anomaly\UsersModule\Http\Middleware\Authenticate',
];

Note that we can also bind simple strings instead of interfaces. This will let us easily resolve the binding out of the container later.

Using the container directly

Within an addon service provider, you always have access to the container via the $this->app property. We can register also bindings using the bind method, passing the class or interface name that we wish to register along with a Closure that returns an instance of the class:

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

Note that we receive the container itself as an argument to the resolver. We can then use the container to resolve sub-dependencies of the object we are building.

An example of using this method within the context of an addon service provider might look like this:

public function register() {
    $this->app->bind('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
}

Binding a Singleton

The singleton method binds a class or interface into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container:

protected $singletons = [
    'Anomaly\UsersModule\User\Contract\UserRepositoryInterface' => 'Anomaly\UsersModule\User\UserRepository',
    'Anomaly\UsersModule\Role\Contract\RoleRepositoryInterface' => 'Anomaly\UsersModule\Role\RoleRepository',
    'Anomaly\UsersModule\Authenticator\Authenticator' => 'Anomaly\UsersModule\Authenticator\Authenticator',
];

You can also define them by access the container directly:

$this->app->singleton('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

Binding an existing object

You may also bind an existing object instance into the container using the instance method. The given instance will always be returned on subsequent calls into the container:

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\Api', $api);

Binding Primitives

Sometimes you may have a class that receives some injected classes, but also needs an injected primitive value such as an integer. You may easily use contextual binding to inject any value your class may need:

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);

Binding Interfaces to Implementations

A very powerful feature of the service container is its ability to bind an interface to a given implementation. For example, let's assume we have an UserRepositoryInterface interface and a CustomUserRepository implementation. Once we have coded our CustomUserRepository implementation of this interface, we can register it with the service container like so:

$this->app->bind(
    'Anomaly\UsersModule\User\Contract\UserRepositoryInterface',
    'App\User\CustomUserRepository'
);

This statement tells the container that it should inject the CustomUserRepository when a class needs an implementation of UserRepositoryInterface. Now we can type-hint the UserRepositoryInterface interface in a constructor, or any other location where dependencies are injected by the service container:

<?php namespace Anomaly\UsersModule\Http\Controller;

use Anomaly\Streams\Platform\Http\Controller\PublicController;
use Anomaly\UsersModule\User\Contract\UserRepositoryInterface;

class UsersController extends PublicController
{

    public function view(UserRepositoryInterface $users)
    {
        if (!$user = $users->findByCustomMethod($this->route->getParameter('username'))) {
            abort(404);
        }

        return $this->view->make('anomaly.module.users::users/view', compact('user'));
    }
}

Contextual Bindings

Sometimes you may have two classes that utilize the same interface, but you wish to inject different implementations into each class. For example, two controllers may depend on different implementations of the Illuminate\Contracts\Filesystem\Filesystem contract. You can leverage Laravel's contextual binding to accomplish this behavior:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
      ->needs(Filesystem::class)
      ->give(function () {
          return Storage::disk('local');
      });

$this->app->when(VideoController::class)
      ->needs(Filesystem::class)
      ->give(function () {
          return Storage::disk('s3');
      });