Use Laravel Contracts to Build a Laravel 5 Twig Package

Share this article

Laravel 5 is finally out, and with all the awesome features it brings. One of the new architectural changes is the new Contracts Package. In this article we are going to understand the reasoning behind this change and try to build a practical use case using the new Contracts.

Laravel

What Is a Contract

A contract is an interface that defines a behaviour. Let’s take this definition from Wikipedia.

In object-oriented languages, the term interface is often used to define an abstract type that contains no data or code, but defines behaviors as method signatures.

With that being said, we use interfaces to extract the object behaviour from a class to an interface and only depend upon the definition. In Laravel’s IoC container you can bind an interface to an implementation.

$this->app->bind('App\Contracts\EventPusher', 'App\Services\PusherEventPusher');

Now every time you resolve the EventPusher interface from the container, you will receive a Pusher implementation. If you decide to switch to another service like Fanout, you only need to change the binding.

$this->app->bind('App\Contracts\EventPusher', 'App\Services\FanoutEventPusher');

Most of Laravel core services now are extracted to a contract, and if you want for example to override the Illuminate/Mail service, you can implement the Illuminate\Contracts\Mail contract and define the necessary methods and add it a s a provider.

Laravel View Contract

Laravel uses its own template engine called Blade. However, I want to use Symfony Twig as my template engine. Blade already offers the possibility to register your own extensions; check the Twig Bridge for more info. Laravel 5 has a better way to achieve the same goal using Contracts, so let’s create our own implementation.

Defining The Package

To start building our package, we need to define it inside the composer.json file. We require Twig and autoload our src folder as our root namespace.

// composer.json
{
  "name": "whyounes/laravel5-twig",
  "description": "Twig for Laravel 5",
  "authors": [
    {
      "name": "RAFIE Younes",
      "email": "younes.rafie@gmail.com"
    }
  ],
  "require": {
    "twig/twig": "1.18.*"
  },
  "autoload": {
    "psr-0": {
      "RAFIE\\": "src/"
    }
  }
}

View Service Provider

The preferable way to register your package bindings is through a Service Provider.

// src/RAFIE/Twig/TwigViewServiceProvider.php

public function registerLoader()
{
  $this->app->singleton('twig.loader', function ($app) {
    $view_paths = $app['config']['view.paths'];
    $loader = new \Twig_Loader_Filesystem($view_paths);

    return $loader;
  });
}

The registerLoader method will bind our Twig Loader to the container. The $app['config']['view.paths'] contains our view paths. By default, it has only the resources/views folder.

// src/RAFIE/Twig/TwigViewServiceProvider.php

public function registerTwig()
{
  $this->app->singleton('twig', function ($app) {
    $options = [
        'debug' => $app['config']['app.debug'],
        'cache' => $app['config']['view.compiled'],
    ];

    $twig = new \Twig_Environment($app['twig.loader'], $options);

    // register Twig Extensions
    $twig->addExtension(new \Twig_Extension_Debug());

    // register Twig globals
    $twig->addGlobal('app', $app);

    return $twig;
  });
}

The Twig_Environment class is the Twig core class. It accepts a Twig_LoaderInterface and a list of options:

  • $app['config']['app.debug'] retrieves our debug flag from the config file.
  • $app['config']['view.compiled'] is our compiled views path registered inside the config/view.php file.

The $twig->addGlobal method registers a variable to be available for all views.

// src/RAFIE/Twig/TwigViewServiceProvider.php

namespace RAFIE\Twig;

class TwigViewServiceProvider extends ServiceProvider
{
	public function register()
	{
	   $this->registerLoader();
	   $this->registerTwig();
	
	   $this->app->bind('view', function ($app) {
	     return new TwigFactory($app);
	   });
	 }
}

The register method will bind the twig and twig.loader to the container. The view key was previously holding the Blade view factory, now it will resolve our new TwigFactory class which will be responsible for rendering our view.
Laravel won’t load your service provider by default, so you need to register it inside your config/app.php providers array. We will also comment out the Laravel view service provider.

// config/app.php

...
'providers' => [
	'RAFIE\Twig\TwigViewServiceProvider',
	//'Illuminate\View\ViewServiceProvider',
...

View Factory

The TwigFactory class must implement the Illuminate\Contracts\View\Factory interface to get the shape and behaviour of the view system. This class will do the job of passing the view to the Twig parser. To achieve more loose coupling, we have a TwigView class which implements the Illuminate\Contracts\View\View contract. This class will behave as a bag for the view object, and will have a reference to the TwigFactory class.

// src/RAFIE/Twig/TwigFactory.php

class TwigFactory implements FactoryContract
{

  /*
   * Twig environment
   *
   * @var Twig_Environment
   * */
  private $twig;

  public function __construct(Application $app)
  {
    $this->twig = $app['twig'];
  }

  public function exists($view)
  {
    return $this->twig->getLoader()->exists($view);
  }

  public function file($path, $data = [], $mergeData = [])
  {
    // or maybe use the String loader
    if (!file_exists($path)) {
      return false;
    }

    $filePath = dirname($path);
    $fileName = basename($path);

    $this->twig->getLoader()->addPath($filePath);

    return new TwigView($this, $fileName, $data);
  }

  public function make($view, $data = [], $mergeData = [])
  {
    $data = array_merge($mergeData, $data);

    return new TwigView($this, $view, $data);
  }

  public function share($key, $value = null)
  {
    $this->twig->addGlobal($key, $value);
  }

  public function render($view, $data)
  {
    return $this->twig->render($view, $data);
  }
	
  public function composer($views, $callback, $priority = null){}

  public function creator($views, $callback){}
  
  public function addNamespace($namespace, $hints){}

}

We resolve the twig object from the container with the parameters explained previously and we start implementing method’s logic. I omitted the last three functions, because defining them will involve creating events and dispatching them, and will make our simple example more complex. The make method returns a new TwigView instance with the current factory, view, and data.

// src/RAFIE/Twig/TwigView.php

use Illuminate\Contracts\View\View as ViewContract;

class TwigView implements ViewContract
{
  /*
   * View name to render
   * @var string
   * */
  private $view;

  /*
   * Data to pass to the view
   * @var array
   * */
  private $data;

  /*
   * Twig factory
   * @var RAFIE\Twig\TwigFactory
   * */
  private $factory;


  public function __construct(TwigFactory $factory, $view, $data = [])
  {
    $this->factory = $factory;
    $this->view = $view;
    $this->data = $data;
  }
  
  public function render()
  {
    return $this->factory->render($this->view, $this->data);
  }

  public function name()
  {
    return $this->view;
  }

  public function with($key, $value = null)
  {
    $this->data[$key] = $value;

    return $this;
  }
  
  public function __toString()
  {
    return $this->render();
  }
}

Let’s take a normal scenario where we have our index page route that returns a home view, and we also provide a variable that ensures our data is passed to the view.

// app/Http/routes.php

Route::get('/', function(){
	return View::make('home.twig', ['name' => 'younes']);
});

// we can also pass the name by chaining the call to the `with` method.
Route::get('/', function(){
	return View::make('home.twig')->with('name', 'younes');
});
// resources/views/home.twig

 <h2>Hello {{ name|upper }}</h2>

Home

When you hit your application’s index page, the execution flow will be:

  • The View facade will be resolved from the container and return a TwigFactory instance.
  • The make method is called and it returns a new TwigView instance with the factory, view and data.
  • Laravel Router accepts a Response object or a string as a request response, so the __toString method is called on our TwigView instance. The render method inside the TwigFactory calls the render method on the Twig_Environment object.

When using contracts, the application API is always consistent, so testing the other implemented methods will be done the same way as before.

// test if a view exists
View::exists('contact.twig');

// render a view from a different folder
View::file('app/contact.twig');

Conclusion

Check out the final version of the project here.

The new Contracts package provides a better way to extend Laravel core components, and provides a stable API for developers to access the framework’s behaviour. Let me know what you think about the new Contracts package in the comments, and if you have any questions don’t hesitate to post them below.

Frequently Asked Questions (FAQs) on Laravel Contracts and Twig Package

What are Laravel Contracts and why are they important?

Laravel Contracts are a set of interfaces that define the core services provided by the Laravel framework. They are important because they provide a clear and consistent way to use these services, regardless of how they are implemented. This allows developers to swap out implementations without changing the code that uses them. Contracts also make it easier to test code, as you can mock the services in your tests.

How do I use Laravel Contracts in my projects?

To use Laravel Contracts in your projects, you first need to understand the contract you want to use. Each contract is an interface that defines a set of methods. You can use these methods in your code by type-hinting the contract in your function or method definitions. Laravel’s service container will automatically inject the correct implementation of the contract.

What is the Laravel 5 Twig package and how does it work with Laravel Contracts?

The Laravel 5 Twig package is a package that allows you to use the Twig templating engine in your Laravel 5 applications. It works with Laravel Contracts by providing a Twig implementation of the View contract. This means you can use Twig to render your views, while still using the same View contract methods you would use with Laravel’s built-in Blade templating engine.

How do I install and configure the Laravel 5 Twig package?

To install the Laravel 5 Twig package, you can use Composer, a dependency management tool for PHP. Once installed, you need to register the Twig service provider and facade in your Laravel application. You can then configure the package by publishing its configuration file and editing it to suit your needs.

Can I use Laravel Contracts with other packages or libraries?

Yes, you can use Laravel Contracts with other packages or libraries. In fact, one of the main benefits of using contracts is that they allow you to decouple your code from specific implementations. This means you can swap out packages or libraries without changing the code that uses them.

What are some common issues I might encounter when using Laravel Contracts?

Some common issues you might encounter when using Laravel Contracts include not understanding the contract you’re using, forgetting to register the service provider for a contract, or trying to use a method that isn’t defined in the contract. It’s important to read the documentation for each contract and understand what methods it provides.

How can I test code that uses Laravel Contracts?

You can test code that uses Laravel Contracts by mocking the contracts in your tests. Laravel provides a variety of helper methods for mocking contracts, which allow you to control the behavior of the contract in your tests. This makes it easier to isolate the code you’re testing and ensure it behaves correctly.

Can I create my own Laravel Contracts?

Yes, you can create your own Laravel Contracts. This can be useful if you want to define a clear interface for a custom service in your application. To create a contract, you simply need to define an interface with the methods your service provides.

How do I use Laravel Contracts with the Laravel 5 Twig package?

To use Laravel Contracts with the Laravel 5 Twig package, you need to type-hint the View contract in your controller methods. The Laravel service container will automatically inject the Twig implementation of the View contract, allowing you to use Twig to render your views.

What are some best practices for using Laravel Contracts?

Some best practices for using Laravel Contracts include understanding the contract you’re using, always type-hinting contracts in your function or method definitions, and using contracts to decouple your code from specific implementations. It’s also a good idea to use contracts in your tests, as this makes it easier to mock services and isolate the code you’re testing.

Younes RafieYounes Rafie
View Author

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.

BrunoScontractsinterfaceslaravelOOPHPPHPsymfonytwig
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week