phplaravelclassextend

How to override View::make() in Laravel 4?


I would like to override the default View::make() method in Laravel, which can be used to return a view-response to the user.

(I think) I've already figured out that this method is stored inside Illuminate\View\Factory.php, and I've been reading about IoC container, while trying to make it work using some similar tutorials, but it just won't work.

I've already created a file App\Lib\MyProject\Extensions\View\Factory.php, containing the following code:

<?php namespace MyProject\Extensions\View;

use Illuminate\View\Factory as OldFactory;

class Factory extends OldFactory {

    public static function make() {
        // My own implementation which should override the default
    }

}

where the MyProject folder is autoloaded with Composer. But I don't know how to use this 'modified' version of the Factory class whenever a static method of View (in particular View::make()) is being called. Some help would be great!


Solution

  • The call to View::make is, in Laravel speak, a call to the View facade. Facades provide global static access to a service in the service container. Step 1 is lookup the actual class View points to

    #File: app/config/app.php
    'aliases' => array(
        'View'            => 'Illuminate\Support\Facades\View',
    )
    

    This means View is aliased to the class Illuminate\Support\Facades\View. If you look at the source of Illuminate\Support\Facades\View

    #File: vendor/laravel/framework/src/Illuminate/Support/Facades/View.php
    class View extends Facade {
    
        /**
         * Get the registered name of the component.
         *
         * @return string
         */
        protected static function getFacadeAccessor() { return 'view'; }
    
    }
    

    You can see the facade service accessor is view. This means a call to

    View::make...
    

    is (more or less) equivalent to a call to

    $app = app();
    $app['view']->make(...
    

    That is, the facade provides access to the view service in the service container. To swap out a different class, all you need to do is bind a different object in as the view service. Laravel provides an extend method for doing this.

    App::extend('view', function(){
        $app = app();
        // Next we need to grab the engine resolver instance that will be used by the
        // environment. The resolver will be used by an environment to get each of
        // the various engine implementations such as plain PHP or Blade engine.
        $resolver = $app['view.engine.resolver'];
    
        $finder = $app['view.finder'];
    
        $env = new \MyProject\Extensions\View($resolver, $finder, $app['events']);
    
        // We will also set the container instance on this view environment since the
        // view composers may be classes registered in the container, which allows
        // for great testable, flexible composers for the application developer.
        $env->setContainer($app);
    
        $env->share('app', $app);
    
        return $env;                
    });
    

    Notice this is more than just instantiating an object. We need to instantiate it the same way as the original view service was instantiated and bound (usually with either bind or bindShared). You can find that code here

    #File: vendor\laravel\framework\src\Illuminate\View\ViewServiceProvider.php
    public function registerFactory()
    {
        $this->app->bindShared('view', function($app)
        {
            // Next we need to grab the engine resolver instance that will be used by the
            // environment. The resolver will be used by an environment to get each of
            // the various engine implementations such as plain PHP or Blade engine.
            $resolver = $app['view.engine.resolver'];
    
            $finder = $app['view.finder'];
    
            $env = new Factory($resolver, $finder, $app['events']);
    
            // We will also set the container instance on this view environment since the
            // view composers may be classes registered in the container, which allows
            // for great testable, flexible composers for the application developer.
            $env->setContainer($app);
    
            $env->share('app', $app);
    
            return $env;
        });
    }
    

    You can test if your binding worked with code like this

    var_dump(get_class(app()['view']));
    

    You should see your class name. Once your sure the binding has "taken", you're free to redefine whatever methods you want.