Extending Multiple Classes In Laravel

Aman Virk picture Aman Virk · Feb 22, 2014 · Viewed 21.8k times · Source

I am stuck as Php natively does not allow extending multiple classes, i am using laravel models to interact with data and want to use 2 different packages to extend it's functionality.

I am trying to use Ardent a validator and Revisionable which maintains update history but i am not sure how to extend these 2 classes within my models.

Any help will be appreciated.

Answer

Antonio Carlos Ribeiro picture Antonio Carlos Ribeiro · Feb 22, 2014

As you know, PHP doesn't support multiple inheritance and unfortunately, this is a PHP deficiency and a problem created by ardent and revisionable package developers. Take Sentry as an example, you can use whatever implementation of Eloquent you want, you just have set the one you need in config.php. Laravel unfortunately has its hands tied, since it is the base class of all those packages. In my opinion.

So you have some options:

1) File an issue on both packages git repositories and hope that one of them changes to support inheritance not only from \Eloquent, but any of the user's classes, extending Eloquent Model abstract class.

You can even change the code of one of those packages and send a pull request to the repository. If it's a good change you probably will see it happening.

2) Use the repository pattern, by creating a class extended from Eloquent, having both of your packages as dependency and, inside it, calling whatever you need from those packages. This will not be a piece of cake, because Eloquent is a complex class and, if you use __call(), you'll have to map all methods you need to forward to your packages, so you don't risk breaking Eloquent's own dynamic calls:

use Ardent;
use Revisionable;

class User extends Eloquent {

    private $ardent;

    privave $revisionable;

    public function __construct(Ardent $ardent, Revisionable $revisionable)
    {
        $this->ardent = $ardent;

        $this->revisionable = $revisionable;
    }

    public function doWhatever() 
    {
        return $this->ardent->do();
    }

    public function __call($name, $arguments)
    {
        if($this->isArdentMethod($name))
        {
            return call_user_func_array(array($this->ardent,$name), $arguments);
        }
        else
        if($this->isRevisionableMethod($name))
        {
            return call_user_func_array(array($this->revisionable,$name), $arguments);
        }
        else
        {
            return parent::__call($name, $arguments);
        }
    }
}

Note that this call

return parent::__call($name, $arguments);

Ensures that all of your calls will be redirected to your Eloquent class, if they are not meant to be executed by one of the others.

As a matter of fact you could even choose one of those, like Ardent as your main class:

class User extends Ardent {}

And just have revisionable as a dependency:

public function __construct(Revisionable $revisionable)

And then forward just revisionable calls to the injected dependency. Because, in the end, those classes are all Eloquent and you can have many Eloquent models pointing to the same tables.

If you look at Revisionable source code you'll that the only call you really have to think of is save(), because it overrides it from Eloquent, all the other you can just call Ardent's.

I know it sounds like a bit too much, but it's doable.