08/17/2018
This is a talk I gave at TrianglePHP on Aug 16, 2018. We'll learn how Eloquent functions on the basic levels, continue through some more well-known methods and some possibly lesser known ones. Then we'll finish with some more advanced ideas and techniques.
Laravel is a modern PHP framework that helps you create applications using simple, expressive syntax as well as offers powerful features like an ORM, routing, queues, events, notifications, simple authentication...
...and so much more!
The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table. Models allow you to query for data in your tables, as well as insert new records into the table.
https://laravel.com/docs/5.6/eloquent
class Post extends Model
{
// look Ma, no code!
}
- id
- title
- created_at
- updated_at
$post = Post::find(1);
php artisan make:model Product
php artisan make:model Product -mcr
-m
will create a migration file
-c
will create a controller
-r
will indicate that controller should be resourceful
$user = new User();
$user->first_name = 'Chris';
$user->email = '[email protected]';
$user->save();
$user = User::create([
'first_name' => 'Chris',
'email' => '[email protected]',
]);
Note: $fillable
/$guarded
properties
$user = User::find(1);
$user->email = '[email protected]';
$user->save();
$user = User::find(1);
$user->update([
'email' => '[email protected]',
]);
Note: $fillable
/$guarded
properties
$user = User::find(1);
$user->fill([
'email' => '[email protected]',
]);
$user->save();
Note: $fillable
/$guarded
properties
$user = User::find(1);
$user->delete();
User::destroy(1);
User::destroy([1, 2, 3]);
User::destroy(1, 2, 3);
User::findOrFail(1);
$user->saveOrFail(); // same as save(), but uses transaction
User::firstOrCreate([ /* attributes */]);
User::updateOrInsert([/* attributes to search */], [/* attributes to update */]);
$users = User::get(); // User::all()
$user = User::where('id', 1)->first();
$user = User::find(1);
$user = User::findOrFail(1);
$users = User::find([1, 2, 3]);
$users = User::whereIn('id', [1, 2, 3])->get();
$users = User::where('is_admin', true)
->where('id', '!=', Auth::id())
->take(10)
->orderBy('last_name', 'ASC')
->get();
User::chunk(50, function ($users) {
foreach ($users as $user) {
//
}
});
For Eloquent methods like
all()
andget()
which retrieve multiple results, an instance ofIlluminate\Database\Eloquent\Collection
will be returned.
$admins = $users->filter(function ($user) {
return $user->is_admin;
});
Product::whereRaw('price > IF(state = "NC", ?, 100)', [200])
->get();
Post::groupBy('category_id')
->havingRaw('COUNT(*) > 1')
->get();
Customer::where('created_at', '>', '2016-01-01')
->orderByRaw('(updated_at - created_at) desc')
->get();
hasOne() // User has one Address
belongsTo() // Address belongs to User
hasMany() // Post has many Comment
belongsToMany() // Role belongs to many User
hasManyThrough() // Country has many Post through User
// Use single table
morphTo() // Comment can be on Post, Video, Album
morphMany() // Post has many Comment
// Use pivot table
morphToMany() // Post has many Tag
morphedByMany() // Tag has many Post
class Video extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
$video = Video::find(1);
foreach ($video->comments as $comment) {
// $comment->body
}
class Video extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
$video = Video::find(1);
foreach ($video->comments()->where('approved', true)->get() as $comment) {
// $comment->body
}
class Video extends Model
{
public function comments()
{
return $this->hasMany(Comment::class)
->where('approved', true)
->latest();
}
}
class Video extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
public function publicComments()
{
return $this->comments()
->where('approved', true)
->latest();
}
}
Default models can be used with belongsTo()
, hasOne()
, and morphOne()
relationships.
{{ $post->author->name }} // error if author not found
class Post extends Model
{
public function author()
{
return $this->belongsTo(User::class);
}
}
{{ $post->author->name ?? '' }} // meh
class Post extends Model
{
public function author()
{
return $this->belongsTo(User::class);
}
}
{{ $post->author->name }} // better!
class Post extends Model
{
public function author()
{
return $this->belongsTo(User::class)->withDefault();
}
}
class Post extends Model
{
public function author()
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
}
The
retrieved
event will fire when an existing model is retrieved from the database. When a new model is saved for the first time, thecreating
andcreated
events will fire. If a model already existed in the database and thesave()
method is called, theupdating
/updated
events will fire. However, in both cases, thesaving
/saved
events will fire.
https://laravel.com/docs/5.6/eloquent#events
class User extends Model
{
protected $dispatchesEvents = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}
php artisan make:observer UserObserver --model=User
class ModelObserverServiceProvider extends ServiceProvider
{
public function boot()
{
User::observe(UserObserver::class);
}
}
class UserObserver
{
public function created(User $user)
{
}
public function updated(User $user)
{
}
public function deleted(User $user)
{
}
}
boot()
methodclass Post extends Model
{
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$model->uuid = (string) Uuid::generate();
});
}
}
class Post extends Model
{
use HasUuid;
}
trait HasUuid
{
public static function bootHasUuid()
{
self::creating(function ($model) {
$model->uuid = (string) Uuid::generate();
});
}
// more uuid related methods
}
$post = Post::find(1);
$post->stars++;
$post->save();
$post->stars--;
$post->save();
$post = Post::find(1);
$post->increment('stars'); // add 1
$post->increment('stars', 15); // add 15
$post->decrement('stars'); // subtract 1
$post->decrement('stars', 15); // subtract 15
$count = Product::where('active', 1)->count();
$min = Product::where('active', 1)->min('price');
$max = Product::where('active', 1)->max('price');
$avg = Product::where('active', 1)->avg('price');
$sum = Product::where('active', 1)->sum('price');
Instead of count()
, you could use...
User::where('username', 'cmgmyr')->exists();
User::where('username', 'cmgmyr')->doesntExist();
$model->isDirty($attributes = null);
$model->isClean($attributes = null);
$model->wasChanged($attributes = null);
$model->hasChanges($changes, $attributes = null);
$model->getDirty();
$model->getChanges();
//Indicates if the model exists.
$model->exists;
//Indicates if the model was inserted during the current request lifecycle.
$model->wasRecentlyCreated;
$users = User::where('approved', 1)->get();
$users = User::whereApproved(1)->get();
$user = User::where('username', 'cmgmyr')->get();
$user = User::whereUsername('cmgmyr')->get();
$admins = User::where('is_admin', true)->get();
$admins = User::whereIsAdmin(true)->get();
User::whereTypeAndStatus('admin', 'active')->get();
User::whereTypeOrStatus('admin', 'active')->get();
https://twitter.com/themsaid/status/1029731544942952448
User::whereDate('created_at', date('Y-m-d'));
User::whereDay('created_at', date('d'));
User::whereMonth('created_at', date('m'));
User::whereYear('created_at', date('Y'));
when()
to eliminate conditionals$query = Author::query();
if (request('filter_by') == 'likes') {
$query->where('likes', '>', request('likes_amount', 0));
}
if (request('filter_by') == 'date') {
$query->orderBy('created_at', request('ordering_rule', 'desc'));
}
when()
to eliminate conditionals$query = Author::query();
$query->when(request('filter_by') == 'likes', function ($q) {
return $q->where('likes', '>', request('likes_amount', 0));
});
$query->when(request('filter_by') == 'date', function ($q) {
return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});
replicate()
a Model$invoice = Invoice::find(1);
$newInvoice = $invoice->replicate();
$newInvoice->save();
// 1, 2, 3, 4, 5...
$users = User::where('active', true)->paginate(15);
// Previous/Next
$users = User::where('active', true)->simplePaginate(15);
// In Blade
{{ $users->links() }}
Pagination to Json
{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"first_page_url": "https://my.app?page=1",
"last_page_url": "https://my.app?page=4",
"next_page_url": "https://my.app?page=2",
"prev_page_url": null,
"path": "https://my.app",
"from": 1,
"to": 15,
"data":[
{
// Result Object
},
{
// Result Object
}
]
}
protected $table = 'users';
protected $fillable = ['first_name', 'email', 'password']; // create()/update()
protected $dates = ['created', 'deleted_at']; // Carbon
protected $appends = ['full_name', 'company']; // additional JSON values
protected $casts = ['is_admin' => 'boolean', 'options' => 'array'];
protected $primaryKey = 'uuid';
public $incrementing = false;
protected $perPage = 25;
const CREATED_AT = 'created';
const UPDATED_AT = 'updated';
public $timestamps = false;
...and more!
updated_at
$product = Product::find(1);
$product->updated_at = '2020-01-01 10:00:00';
$product->save(['timestamps' => false]);
$video = Video::find(1);
$video->getKeyName(); // 'id'
$video->getKeyType(); // 'int'
$video->getKey(); // 1
class User extends Model
{
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
public function setLastNameAttribute($value)
{
$this->attributes['last_name'] = strtolower($value);
}
}
class User extends Model
{
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
public function getLastNameAttribute($value)
{
return ucfirst($value);
}
public function getEmailAttribute($value)
{
return new Email($value);
}
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
$user = User::create([
'first_name' => 'Chris', // chris
'last_name' => 'Gmyr', // gmyr
'email' => '[email protected]',
]);
$user->first_name; // Chris
$user->last_name; // Gmyr
$user->email; // instance of Email
$user->full_name; // 'Chris Gmyr'
$user = User::find(1);
return $user->toArray();
return $user->toJson();
You can also return $user
from a controller method and it will automatically return JSON.
class User extends Model
{
protected $appends = ['full_name']; // adds to toArray()
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
// or...
return $user->append('full_name')->toArray();
return $user->setAppends(['full_name'])->toArray();
$posts = Post::whereNotNull('published_at')
->where('published_at', '<=', Carbon::now())
->latest('published_at')
->get();
class Post extends Model
{
public function scopePublished($query)
{
return $query->whereNotNull('published_at')
->where('published_at', '<=', Carbon::now())
->latest('published_at');
}
}
$posts = Post::published()->get();
$admins = User::where('is_admin', true)->get();
$customers = User::where('is_admin', false)->get();
class User extends Model
{
public function scopeAdmin($query)
{
return $query->where('is_admin', true);
}
public function scopeCustomer($query)
{
return $query->where('is_admin', false);
}
}
$admins = User::admin()->get();
$customers = User::customer()->get();
class Admin extends User
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(function ($query) {
$query->where('is_admin', true);
});
}
}
class Customer extends User
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(function ($query) {
$query->where('is_admin', false);
});
}
}
$admins = Admin::get();
$customers = Customer::get();
Read more:
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->string('role')->default('user'); // moderator, admin, etc
$table->rememberToken();
$table->timestamps();
});
class User extends Model
{
protected $fillable = [
'name', 'email', 'password', 'role'
];
}
$user = new User();
$user->name = 'Chris';
$user->email = '[email protected]';
$user->password = Hash::make('p@ssw0rd');
// $user->role is currently NULL
$user->save();
$user->role; // 'user'
Remove ->default('user')
;
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->string('role'); // moderator, admin, etc
$table->rememberToken();
$table->timestamps();
});
Set $attributes
class User extends Model
{
protected $fillable = [
'name', 'email', 'password', 'role'
];
protected $attributes = [
'role' => 'user',
];
}
$user = new User();
$user->name = 'Chris';
$user->email = '[email protected]';
$user->password = Hash::make('p@ssw0rd');
// $user->role is currently 'user'!
$user->save();
$user->role; // 'user'
$user = new User();
$user->name = 'Chris';
$user->email = '[email protected]';
$user->password = Hash::make('p@ssw0rd');
$user->role = 'admin'; // can override default
$user->save();
$user->role; // 'admin'
Remember our previous example?
class Post extends Model
{
public function author()
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
}
We no longer need to provide a name
, use the User $attributes
property!
class Post extends Model
{
public function author()
{
return $this->belongsTo(User::class)->withDefault();
}
}
class User extends Model
{
protected $fillable = [
'name', 'email', 'password', 'role'
];
protected $attributes = [
'name' => 'Guest Author',
'role' => 'user',
];
}
Watch Colin DeCarlo's - Keeping Eloquent Eloquent from Laracon US 2016
https://streamacon.com/video/laracon-us-2016/colin-decarlo-keeping-eloquent-eloquent
$customers = Customer::with('company')
->orderByName()
->paginate();
Get latest interactions?
<p>{{ $customer
->interactions()
->latest()
->first()
->created_at
->diffForHumans() }}</p>
public function scopeWithLastInteractionDate($query)
{
$subQuery = \DB::table('interactions')
->select('created_at')
->whereRaw('customer_id = customers.id')
->latest()
->limit(1);
return $query->select('customers.*')->selectSub($subQuery, 'last_interaction_date');
}
$customers = Customer::with('company')
->withLastInteractionDate()
->orderByName()
->paginate();
<p>{{ $customer->last_interaction_date->diffForHumans() }}</p>
Jonathan Reinink's Laracon 2018 Online Talk - Advanced Querying with Eloquent
https://github.com/reinink/laracon2018