Eloquent relationships for not-existing model clas

2019-07-22 00:52发布

问题:

I would like to have in my applications many models/modules but some of them would be removed for some clients.

Now I have such relation:

public function people()
{
    return $this->hasMany('People', 'model_id');
}

and when I run $model = Model::with('people')->get(); it is working fine

But what if the People model doesn't exist?

At the moment I'm getting:

1/1 ErrorException in ClassLoader.php line 386: include(...): failed to open stream: No such file or directory

I tried with

public function people()
{ 
    try {
       return $this->hasMany('People', 'model_id');
    }
    catch (FatalErrorException $e) {
        return null;
    }
}

or with:

public function people()
{ 
    return null; // here I could add checking if there is a Model class and if not return null
}

but when using such method $model = Model::with('people')->get(); doesn't work.

I will have a dozens of relations and I cannot have list of them to use in with. The best method for that would be using some empty relation (returning null) just to make Eloquent not to do anything but in this case Eloquent still tries to make it work and I will get:

Whoops, looks like something went wrong. 1/1 FatalErrorException in Builder.php line 430: Call to a member function addEagerConstraints() on null

Is there any simple solution for that?

回答1:

The only solution I could come up with is creating your own Eloquent\Builder class.

I've called it MyBuilder. Let's first make sure it gets actually used. In your model (preferably a Base Model) add this newEloquentBuilder method:

public function newEloquentBuilder($query)
{
    return new MyBuilder($query);
}

In the custom Builder class we will override the loadRelation method and add an if null check right before addEagerConstraints is called on the relation (or in your case on null)

class MyBuilder extends \Illuminate\Database\Eloquent\Builder {
    protected function loadRelation(array $models, $name, Closure $constraints)
    {
        $relation = $this->getRelation($name);


        if($relation == null){
            return $models;
        }


        $relation->addEagerConstraints($models);

        call_user_func($constraints, $relation);

        $models = $relation->initRelation($models, $name);

        $results = $relation->getEager();

        return $relation->match($models, $results, $name);
    }
}

The rest of the function is basically the identical code from the original builder (Illuminate\Database\Eloquent\Builder)

Now simply add something like this in your relation function and it should all work:

public function people()
{
    if(!class_exist('People')){
        return null;
    }
    return $this->hasMany('People', 'model_id');
}

Update: Use it like a relationship

If you want to use it like you can with a relationship it gets a bit more tricky.

You have to override the getRelationshipFromMethod function in Eloquent\Model. So let's create a Base Model (Your model obviously needs to extend it then...)

class BaseModel extends \Illuminate\Database\Eloquent\Model {
    protected function getRelationshipFromMethod($key, $camelKey)
    {
        $relations = $this->$camelKey();

        if ( $relations instanceof \Illuminate\Database\Eloquent\Collection){
            // "fake" relationship
            return $this->relations[$key] = $relations;
        }
        if ( ! $relations instanceof Relation)
        {
            throw new LogicException('Relationship method must return an object of type '
                . 'Illuminate\Database\Eloquent\Relations\Relation');
        }

        return $this->relations[$key] = $relations->getResults();
    }
}

Now we need to modify the relation to return an empty collection

public function people()
{
    if(!class_exist('People')){
        return new \Illuminate\Database\Eloquent\Collection();
    }
    return $this->hasMany('People', 'model_id');
}

And change the loadRelation function in MyBuilder to check for the type collection instead of null

protected function loadRelation(array $models, $name, Closure $constraints)
{
    $relation = $this->getRelation($name);

    if($relation instanceof \Illuminate\Database\Eloquent\Collection){
        return $models;
    }

    // ...
}