Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to replace a service in a Laravel Service Provider?

Tags:

php

laravel

I want to replace a service provided by the Laravel framework to one of my own, that extends that same Laravel service. In this specific case, I want to exchange the BladeCompiler service.

The default ViewServiceProvider provided by Laravel does something like this:

use Illuminate\View\Engines\BladeCompiler;
use Illuminate\View\Engines\CompilerEngine;

...

public function registerBladeEngine($resolver)
{
    $app = $this->app;

    $resolver->register('blade', function() use ($app)
    {
        $cache = $app['path.storage'].'/views';

        $compiler = new BladeCompiler($app['files'], $cache);

        return new CompilerEngine($compiler, $app['files']);
    });
}

The only thing I need to do, is extend the provider, override the method and replace the compiler class. In this case, My\View\Engines\BladeCompiler.

But to do that, I have to literally copy and paste all the function to my service provider, and just replace the use statement. That's a lousy way to do that, since any modification on that part of Laravel will break my app.

What I really want to do, is create another class that extends the default blade compiler class, to add more features to it.

Does anybody has a better idea?

EDIT:

I opened an issue describing that problem, and Taylor will make a change for 4.1 that makes extending a service much more easier (at least the blade compiler).

like image 614
Denis Lins Avatar asked Nov 20 '13 17:11

Denis Lins


People also ask

What is the difference between service provider and service container in Laravel?

Service container is the place our application bindings are stored. And the service providers are the classes where we register our bindings to service container. In older releases of Laravel, we didn't have these providers and people were always asking where to put the bindings.

What is the purpose of service provider in Laravel?

Service providers are the central place to configure your application. If you open the config/app. php file included with Laravel, you will see a providers array. These are all of the service provider classes that will be loaded for your application.

What is AppServiceProvider in Laravel?

AppServiceProvider is for Laravel-specific services that you've overridden (or actually specified), such as the Illuminate\Contracts\Auth\Registrar , The HTTP/Console Kernels, and anything you wish to override in Laravel. This is a single service provider, that registers container bindings that you specify.


2 Answers

I believe you have two options:

  1. Overwrite the service provider with your own.
  2. Take advantage of the BladeCompiler's extend() method.

Option 1: Service provider copy & paste

The key thing you want to do is overwrite the Blade compiler with your extended version. Because of that whole inheritance thing, you have to define it in pretty much the exact same way as the default service provider; particularly this chunk of code:

$resolver->register('blade', function() use ($app)
{
    $cache = $app['path.storage'].'/views';

    // This is where your extended class would go.
    $compiler = new BladeCompiler($app['files'], $cache);

    return new CompilerEngine($compiler, $app['files']);
});

You can access the View class' EngineResolver in a service provider via:

$this->app['view']->getEngineResolver()

All put together, you're left with something along these lines (replacing class names and modifying use where appropriate):

<?php

use Illuminate\Support\ServiceProvider;
use Illuminate\View\Engines\CompilerEngine;

class ExtendBladeServiceProvider extends ServiceProvider {

    public function register()
    {
        $resolver = $this->app['view']->getEngineResolver();

        $resolver->register('blade', function() use ($app)
        {
            $cache = $app['path.storage'].'/views';

            $compiler = new MyBladeCompiler($app['files'], $cache);

            return new CompilerEngine($compiler, $app['files']);
        });
    }
}

So yes, you are basically duplicating the code from the existing service provider. But you pretty much have to, to the best of my knowledge. I'm not sure why you're worried about changes to Laravel braking your application; you're literally depending on Laravel components, so potentially any changes will break things. That's a risk you take using components that you have no control over.

Option 2: BladeCompiler's extend() method.

This you can do pretty much anywhere it makes sense in your application. You won't be extending the BladeCompiler class in a traditional sense, but you can implement your own set of compile functions to run.

extend() adds the provided closure to an array that is iterated upon during compiling. It is essentially the same as extended class methods, and does not require you to re-instantiate things like the CompileEngine.

One option to register these extensions is to use a service provider's boot() method (you can use register() too, just make sure you do it in the right order):

public function boot()
{
    $blade = $this->app['view']->getEngineResolver()->resolve('blade')->getCompiler();

    $blade->extend(function($value) use ($blade)
    {
        $matcher = $blade->createMatcher('myfeature');

        return preg_replace($matcher, '<?php echo MyFeature::make$2; ?>', $value);
    });
}
like image 147
Aken Roberts Avatar answered Oct 22 '22 06:10

Aken Roberts


You might look at the service provider for (the awesome) TwigBridge package. What that package does is register another view handler (in this case, .twig files). Since you probably want to have a different extension for your extended blade files (.bladex ?) you could do something like

[warning: untested code]

$app['view']->addExtension('bladex','bladex',function() use($app) 
{
   $cache = $app['path.storage'].'/views';
   $compiler = new BladeXCompiler($app['files'], $cache);
   return new CompilerEngine($compiler, $app['files']);
}
like image 45
J.T. Grimes Avatar answered Oct 22 '22 06:10

J.T. Grimes