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?
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;
}
// ...
}