Eloquent is a great feature of Laravel, but also great is the list of packages that add more features on top of the framework. Let's explore them, in this article!
The title says 20 packages, but there are quite a few alternatives mentioned along the way, so the actual number is even bigger than this. Ready? Let's jump in!
GitHub: https://github.com/spatie/laravel-query-builder
This package allows you to filter, sort and include eloquent relations based on a request.
Your users may type something like:
Then, your Controller will filter the records based on those parameters.
Code example:
$users = QueryBuilder::for(User::class) ->allowedFilters('name') ->allowedIncludes('posts') ->allowedSorts('id') ->get();
It reminds me of GraphQL logic where the server defines the rules for possible queries, and the client has freedom to choose what and how exactly they want to get in the results. I have a full Laravel+GraphQL course, if you're interested.
Alternative similar package: https://github.com/Tucker-Eric/EloquentFilter
GitHub: https://github.com/spatie/laravel-searchable
This package makes it easy to get structured search from a variety of Eloquent models, like a global search in your whole project.
How does it work?
First, you prepare your Models, providing what fields identify each of them:
use Spatie\Searchable\Searchable;use Spatie\Searchable\SearchResult; class Post extends Model implements Searchable{ public function getSearchResult(): SearchResult { $url = route('posts.show', $this->slug); return new \Spatie\Searchable\SearchResult( $this, $this->title, $url ); }}
Similarly, you define other search result parameters for User model and other models.
Then, in your Controller, you may have this:
$searchResults = (new Search()) ->registerModel(User::class, 'name') ->registerModel(Post::class, 'title') ->search('john');
The results will contain a multi-dimensional Eloquent collection, which you can iterate by various methods of the package. Here's a Blade example from the docs:
@foreach($searchResults->groupByType() as $type => $modelSearchResults) <h2>{{ $type }}</h2> @foreach($modelSearchResults as $searchResult) <ul> <li><a href="{{ $searchResult->url }}">{{ $searchResult->title }}</a></li> </ul> @endforeach@endforeach
Alternative similar package: https://github.com/protonemedia/laravel-cross-eloquent-search
GitHub: https://github.com/staudenmeir/eloquent-has-many-deep
This extended version of HasManyThrough
allows relationships with unlimited intermediate models. It supports many-to-many and polymorphic relationships and all their possible combinations.
Consider this example from the Laravel documentation with an additional level:
Country
→ has many → User
→ has many → Post
→ has many → Comment
class Country extends Model{ use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments() { return $this->hasManyDeep(Comment::class, [User::class, Post::class]); }}
Then, you can use the N-th level relationship like a simple hasManyThrough
:
Country::with('comments')->get();
I also recommend other Eloquent-related packages by Jonas Staudenmeir:
GitHub: https://github.com/tighten/parental
Parental is a Laravel package that brings STI (Single Table Inheritance) capabilities to Eloquent. It's a fancy name for a simple concept: Extending a model, but referencing the same table.
Simple usage:
// The "parent"class User extends Model{ use HasChildren;} // The "child"class Admin extends User{ use HasParent; public function impersonate($user) { //... }}
Then, in the Controller:
use App\Models\Admin; // Returns "Admin" model, but reference "users" table:$admin = Admin::first(); // Can now access behavior exclusive to "Admin"s$admin->impersonate($user);
Without Parental, calling Admin::first()
would throw an error because Laravel would be looking for an admins
table. Laravel generates expected table names, as well as foreign keys and pivot table names, using the model's class name. By adding the HasParent
trait to the Admin
model, Laravel will now reference the parent model's class name users
.
GitHub: https://github.com/michaeldyrynda/laravel-cascade-soft-deletes
This package solves a problem: when you use Soft Deletes in a parent-child hasMany
relationship, if you soft-delete a parent record, children records do NOT get automatically soft-deleted.
For example, if you have Post
-> hasMany -> Comment
, both Models use Soft Deletes, and you want to delete the Post with its comments automatically, you need to provide this in the Post model:
class Post extends Model{ use SoftDeletes, CascadeSoftDeletes; protected $cascadeDeletes = ['comments']; public function comments() { return $this->hasMany(Comment::class); }}
Now you can delete a Post
record with $post->delete()
, and any associated Comment
records will be deleted.
GitHub: https://github.com/spatie/laravel-model-status
Assign statuses to Eloquent models.
Imagine you want to have an Eloquent model hold a status. It's easily solved by just adding a status
field to that model and be done with it, right?
But in case you need a history of status changes or need to store some extra info on why a status changed...
Just add a trait HasStatuses
to your model:
use Spatie\ModelStatus\HasStatuses; class YourEloquentModel extends Model{ use HasStatuses;}
And then, in Controller, or wherever, you may perform these manipulations:
// set a status$model->setStatus('pending', 'needs verification'); // set another status$model->setStatus('accepted'); // specify a reason$model->setStatus('rejected', 'My rejection reason'); // get the current status$model->status(); // returns an instance of \Spatie\ModelStatus\Status // get the previous status$latestPendingStatus = $model->latestStatus('pending'); $latestPendingStatus->reason; // returns 'needs verification'
GitHub: https://github.com/spatie/laravel-model-flags
Similar to the previous one about statuses, you may flag certain models with some condition, without having to add an additional column using migrations.
Code examples:
$user->hasFlag('receivedMail'); // returns false $user->flag('receivedMail'); // flag the user as having received the mail $user->hasFlag('receivedMail'); // returns trueIt also provides scopes to quickly query all models with a certain flag. User::flagged('myFlag')->get(); // returns all models with the given flagUser::notFlagged('myFlag')->get(); // returns all models without the given flag
GitHub: https://github.com/owen-it/laravel-auditing
Laravel Auditing allows you to keep a history of model changes by simply using a trait. Retrieving the audited data is straightforward, making it possible to display it in various ways.
Just add a trait to your model:
class Article extends Model implements Auditable{ use \OwenIt\Auditing\Auditable; // ...}
And then, you can retrieve the records:
// Get first available Article$article = Article::first(); // Get all associated Audits$all = $article->audits; // Get first Audit$first = $article->audits()->first(); // Get last Audit$last = $article->audits()->latest()->first(); // Get Audit by id$audit = $article->audits()->find(4);
There are alternative similar packages:
GitHub: https://github.com/spatie/laravel-medialibrary
Associate files with Eloquent models. This package can associate all sorts of files with Eloquent models, but mainly is used for saving images.
With this package, you can do something like:
$newsItem->addMedia($pathToFile)->toMediaCollection('images');
For that, prepare the Model with the interface and the trait:
use Spatie\MediaLibrary\HasMedia;use Spatie\MediaLibrary\InteractsWithMedia; class User extends Model implements HasMedia{ use InteractsWithMedia;}
The information about the files is saved in the media
DB table, using polymorphic relations.
You may also define the thumbnail image sizes (named "conversions") to be generated, whenever the image is saved, add this method in the same model:
use Spatie\MediaLibrary\MediaCollections\Models\Media; public function registerMediaConversions(Media $media = null): void{ $this ->addMediaConversion('preview') ->fit(Manipulations::FIT_CROP, 300, 300) ->nonQueued();}
Then, you can retrieve the media items of the model in various ways:
$mediaItems = $user->getMedia();$url = $user->getFirstMediaUrl();
GitHub: https://github.com/spatie/laravel-translatable
Another package from Spatie, surprised? :)
This is a trait to make Eloquent models translatable. Translations are stored as json, so no extra table needed.
Code example:
$newsItem = new NewsItem; // This is an Eloquent model$newsItem ->setTranslation('name', 'en', 'Name in English') ->setTranslation('name', 'nl', 'Naam in het Nederlands') ->save(); $newsItem->name; // Returns 'Name in English' given that the current app locale is 'en'$newsItem->getTranslation('name', 'nl'); // returns 'Naam in het Nederlands' app()->setLocale('nl'); $newsItem->name; // Returns 'Naam in het Nederlands'
GitHub: https://github.com/spatie/laravel-tags
This package offers taggable behaviour for your models.
Just add a trait to the model:
use Spatie\Tags\HasTags; class NewsItem extends Model{ use HasTags;}
Then, you can perform manipulations like these:
// create a model with some tags$newsItem = NewsItem::create([ 'name' => 'The Article Title', 'tags' => ['first tag', 'second tag'], //tags will be created if they don't exist]); // attaching tags$newsItem->attachTag('third tag');$newsItem->attachTag('third tag','some_type');$newsItem->attachTags(['fourth tag', 'fifth tag']); // retrieving tags with a type$newsItem->tagsWithType('categories');$newsItem->tagsWithType('topics');
In addition to just tagging, this package has support for translating tags, multiple tag types and sorting capabilities.
Alternative similar package: https://github.com/rtconner/laravel-tagging
GitHub: https://github.com/spatie/laravel-sluggable
This package provides a trait that will generate a unique slug when saving any Eloquent model.
Add a trait to your model, but you also need to implement the logic of the slugs yourself:
use Spatie\Sluggable\HasSlug;use Spatie\Sluggable\SlugOptions; class YourEloquentModel extends Model{ use HasSlug; public function getSlugOptions() : SlugOptions { return SlugOptions::create() ->generateSlugsFrom('name') ->saveSlugsTo('slug'); // you need to create this column in DB }}
Then, in your Controller, you may do something like this:
$post = new Post();$post->name = 'activerecord is awesome';$post->save(); echo $post->slug; // outputs "activerecord-is-awesome"
Someone may ask, why you would need a package when there's a default Laravel Str::slug()
method?
The thing is that this package takes care of unique slugs as well, and has more options to configure.
Alternative similar package: https://github.com/cviebrock/eloquent-sluggable
GitHub: https://github.com/cybercog/laravel-love
Laravel Love lets people express how they feel about the content, by making any model reactable.
In your model, add Reacterable
to who can react to items:
class User extends Authenticatable implements ReacterableInterface{ use Reacterable;}
In the model of what to react to, add these:
class Comment extends Model implements ReactableInterface{ use Reactable;}
And then, in Controllers, or wherever, you can use these:
$reacter->reactTo($reactant, $reactionType);$reacter->reactTo($reactant, $reactionType, 4.0);$isNotReacted = $reacter->hasNotReactedTo($reactant);
GitHub: https://github.com/maize-tech/laravel-markable
A similar package to the one above, just maybe easier to use. This package allows you to easily add the markable feature to your application, as for example likes, bookmarks, favorites and so on.
In your model, add these:
use Maize\Markable\Markable;use Maize\Markable\Models\Like; class Course extends Model{ use Markable; protected static $marks = [ Like::class, ];}
And then, use it like this:
$course = Course::firstOrFail();$user = auth()->user(); Like::add($course, $user); // marks the course liked for the given user Like::count($course); // returns the amount of like marks for the given course
By default, the package supports four "marks":
You may also implement your own custom mark.
GitHub: https://github.com/spatie/laravel-schemaless-attributes
Add schemaless attributes to Eloquent models. Wouldn't it be cool if you could have a bit of the spirit of NoSQL available in Eloquent? This package does just that. It provides a trait that when applied on a model, allows you to store arbitrary values in a single JSON column.
To use, add a migration for all models where you want to add schemaless attributes to:
Schema::table('your_models', function (Blueprint $table) { $table->schemalessAttributes('extra_attributes');});
Then, prepare the model:
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; class TestModel extends Model{ public $casts = [ 'extra_attributes' => SchemalessAttributes::class, ]; public function scopeWithExtraAttributes(): Builder { return $this->extra_attributes->modelScope(); } // ...}
And then, you can use it like this:
// add and retrieve an attribute$yourModel->extra_attributes->name = 'value';$yourModel->extra_attributes->name; // returns 'value' // you can also use the array approach$yourModel->extra_attributes['name'] = 'value';$yourModel->extra_attributes['name'] // returns 'value' // setting multiple values in one go$yourModel->extra_attributes = [ 'rey' => ['side' => 'light'], 'snoke' => ['side' => 'dark']];
GitHub: https://github.com/kirschbaum-development/eloquent-power-joins
This package allows to use a different syntax for DB joins, kind of a middle ground between Eloquent and Query Builder.
You can read a more detailed explanation on the problems this package solves on this blog post.
In your Model, define this:
use Kirschbaum\PowerJoins\PowerJoins; class User extends Model{ use PowerJoins;}
Then, you can use the joinRelationship()
syntax:
User::joinRelationship('posts', function ($join) { $join->where('posts.approved', true);})->toSql();
GitHub: https://github.com/spatie/eloquent-sortable
This package provides a trait that adds sortable behaviour to an Eloquent model.
In your model, add this:
class MyModel extends Model implements Sortable{ use SortableTrait; public $sortable = [ 'order_column_name' => 'order_column', 'sort_when_creating' => true, ];}
You need to have order_column
in your DB table, that field would be used for sorting.
Then, when creating new records, that field name would be auto-saved:
$myModel = new MyModel();$myModel->save(); // order_column for this record will be set to 1 $myModel = new MyModel();$myModel->save(); // order_column for this record will be set to 2
And then, you can get the ordered records:
$orderedRecords = MyModel::ordered()->get();
And you can easily change the order of a current object, with these methods:
$myModel->moveOrderDown();$myModel->moveOrderUp();$myModel->moveToStart();$myModel->moveToEnd();
Alternative similar packages:
GitHub: https://github.com/topclaudy/compoships
Compoships offers the ability to specify relationships based on two (or more) columns in Laravel's Eloquent.
Code example:
namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model{ use \Awobaz\Compoships\Compoships; public function tasks() { return $this->hasMany(Task::class, ['team_id', 'category_id'], ['team_id', 'category_id']); // Schematically: // return $this->hasMany('B', // ['foreignKey1', 'foreignKey2'], // ['localKey1', 'localKey2']); }}
GitHub: https://github.com/cjmellor/approval
Easily approve new Model data before it persists. On the Model, add the MustBeApproved
trait.
use Cjmellor\Approval\Concerns\MustBeApproved; class Post extends Model{ use MustBeApproved; // ...}
When a record is saved, it is stored in a JSON field on the approvals
table. The package has scopes and helpers to get approval records.
Scopes:
Approval::approved()->get();Approval::rejected()->get();Approval::pending()->count();
Approval::where('id', 1)->approve();Approval::where('id', 2)->reject();Approval::where('id', 3)->postpone();
Helpers:
$approval->approveIf(true);$approval->rejectIf(false);$approval->postponeIf(true); $approval->approveUnless(false);$approval->rejectUnless(true);$approval->postponeUnless(false);
GitHub: https://github.com/oddvalue/laravel-drafts
A simple way to add drafts/revisions to your Models. Just add the HasDrafts
trait to your Model.
use Oddvalue\LaravelDrafts\Concerns\HasDrafts; class Post extends Model{ use HasDrafts; ...}
The package has helpers for creating fields in the Migration.
Schema::table('posts', function (Blueprint $table) { $table->drafts();}); Schema::table('posts', function (Blueprint $table) { $table->dropDrafts();});
By default, new records will be created as published. You can change this either by including 'is_published' => false
in the attributes of the model or by using the createDraft
or saveAsDraft
methods.
Post::create([ 'title' => 'Foo', 'is_published' => false,]); # OR Post::createDraft(['title' => 'Foo']); # OR Post::make(['title' => 'Foo'])->saveAsDraft();
When saving/updating a record, the published state is maintained. If you want to save a draft of a published record, you can use the saveAsDraft
and updateAsDraft
methods.
# Create published post$post = Post::create(['title' => 'Foo']); # Create drafted copy $post->updateAsDraft(['title' => 'Bar']); # OR $post->title = 'Bar';$post->saveAsDraft();
This will create a draft record, and the original record will be left unchanged.
# | title | uuid | published_at | is_published | is_current | created_at | updated_at |
---|---|---|---|---|---|---|---|
1 | Foo | 9188eb5b-cc42-47e9-aec3-d396666b4e80 | 2000-01-01 00:00:00 | 1 | 0 | 2000-01-01 00:00:00 | 2000-01-01 00:00:00 |
2 | Bar | 9188eb5b-cc42-47e9-aec3-d396666b4e80 | 2000-01-02 00:00:00 | 0 | 1 | 2000-01-02 00:00:00 | 2000-01-02 00:00:00 |
So that's it for this long list, but it may be even longer. Did we miss any package? Add them in the comments!