Let's get a bit more practical. Quite often, you want to use a Laravel package but slightly change its functionality. You can't edit anything in /vendor
because package updates would wipe out your changes, right? So, I will show you what to do, with two examples.
Here's a situation we had at our own Laravel Daily. We're using the spatie/laravel-tags package, which is configured by just adding a Trait to our Model:
app/Models/Course.php:
use Spatie\Tags\HasTags; // ... class Course extends Model{ use HasTags; // ...
Inside that trait, there's a polymorphic relation method:
vendor/spatie/laravel-tags/src/HasTags.php:
trait HasTags { // ... public function tags(): MorphToMany { return $this ->morphToMany(self::getTagClassName(), $this->getTaggableMorphName()) ->using($this->getPivotModelClassName()) ->ordered(); }}
But what we needed in our project was to automatically order that relationship by a custom field order_column
that we added to the taggables
DB table.
So, we have overridden this method, changed a few more of its pieces, and ended up with this:
app/Models/Course.php:
use Spatie\Tags\HasTags; // ... class Course extends Model{ use HasTags; public function tags(): MorphToMany { return $this ->morphToMany(self::getTagClassName(), 'taggable', 'taggables', null, 'tag_id') ->orderBy('order_column'); }
So now, whenever $course->tags()
is called, it will use our method from the Model instead of the package's Trait method.
The only rule here is that our method needs to be identical to the one from the Trait: the same name, parameter list, and return type.
The second example is related to the same Spatie Tags package but from a different angle. What if you want to add more functions to the default Tag Model from the package?
For example, we wanted to add extra DB tables related to the default tags
DB table from the package.
The goal was to have this code in the Controller:
$tag = Tag::with('links.linkType')->slug($slug)->firstOrFail();
Here we have two things that the package doesn't have:
links
DB table->slug()
So, we defined our own Tag Model:
php artisan make:model Tag
Then, we changed the extends Model
to extend the Spatie Tag Model and added a few features.
app/Models/Tag.php:
namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Database\Eloquent\Relations\MorphToMany;use Spatie\Tags\Tag as SpatieTag; class Tag extends SpatieTag{ use HasFactory; public function scopeSlug($query, string $slug, string $type = null, string $locale = null) { $locale ??= static::getLocale(); return $query->where("slug->{$locale}", $slug)->where('type', $type); } public function links(): MorphToMany { return $this->morphedByMany(Link::class, 'taggable')->withPivot('priority'); } // ... more methods}
See the alias we had to make? We added as SpatieTag
to avoid the conflict.
And then, we add more relationships, scopes, and whatever else we want.
As a result, in the Controllers, we use the use App\Models\Tag
instead of use Spatie\Tags\Tag
on top.
But since our Model extends the Model from the package, we still have the same functionality. We just added some more on top of it.
So, this is how you can use OOP knowledge to override/extend the package behavior in PHP/Laravel.