Eloquent performance quick tip

Improve Laravel performance with this simple trick!


If you use Eloquent relationships, even if you have followed Laravel's naming convention, always provide the $name/$relation parameter for the relationship methods like belongsTo()morphTo()belongsToMany() or morphToMany(), regardless.

Example from my codebase:

public function recordable()
{ 
    return $this->morphTo(__FUNCTION__);
}

public function city()
{
    return $this->belongsTo(Country::class, 'country_id', 'id', __FUNCTION__)
}

If you don't provide that argument, Laravel will use the debug_backtrace() to get the relation name, which is resource intensive, and those Eloquent relationship calls can quickly add up.

Take a look at Laravel's internals:

// MorphTo Example

public function morphTo($name = null, $type = null, $id = null)
{
        
   $name = $name ?: $this->guessBelongsToRelation();

   list($type, $id) = $this->getMorphs(
       Str::snake($name), $type, $id
   );

        
   return empty($class = $this->{$type})
                ? $this->morphEagerTo($name, $type, $id)
                : $this->morphInstanceTo($class, $name, $type, $id);
}

protected function guessBelongsToRelation()
{
   list($one, $two, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);

   return $caller['function'];
}

// BelongsToMany Example

public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null)
{
      
   if (is_null($relation)) {
      $relation = $this->guessBelongsToManyRelation();
   }

   $instance = $this->newRelatedInstance($related);

   $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();

   $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();

   if (is_null($table)) {
       $table = $this->joiningTable($related);
   }

   return $this->newBelongsToMany(
       $instance->newQuery(), $this, $table, $foreignPivotKey,
       $relatedPivotKey, $parentKey ?: $this->getKeyName(),
       $relatedKey ?: $instance->getKeyName(), $relation
   );
}

protected function guessBelongsToManyRelation()
{
   $caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) {
       return ! in_array($trace['function'], Model::$manyMethods);
   });

   return ! is_null($caller) ? $caller['function'] : null; 
}

That's it, folks, I said it was a quick tip! ;)

 

P.S. Many micros make a macro, so don't come at me with the "That's a micro-optimization argument", that's not an excuse for being lazy :)

Last updated: 1 year ago 2561