Slide 1

Slide 1 text

TIPS, TRICKS, AND GOOD PRACTICES WITH LARAVEL'S ELOQUENT PRESENTED BY CHRIS GMYR

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

WHAT IS LARAVEL? 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! 3 — Twitter/GitHub: @cmgmyr

Slide 4

Slide 4 text

WHAT IS ELOQUENT? 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 4 — Twitter/GitHub: @cmgmyr

Slide 5

Slide 5 text

THE BASICS

Slide 6

Slide 6 text

A MODEL class Post extends Model { // look Ma, no code! } - id - title - created_at - updated_at $post = Post::find(1); 6 — Twitter/GitHub: @cmgmyr

Slide 7

Slide 7 text

ARTISAN GOODIES php artisan make:model Product 7 — Twitter/GitHub: @cmgmyr

Slide 8

Slide 8 text

ARTISAN GOODIES 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 8 — Twitter/GitHub: @cmgmyr

Slide 9

Slide 9 text

CRUDDY

Slide 10

Slide 10 text

CREATING $user = new User(); $user->first_name = 'Chris'; $user->email = '[email protected]'; $user->save(); 10 — Twitter/GitHub: @cmgmyr

Slide 11

Slide 11 text

CREATING $user = User::create([ 'first_name' => 'Chris', 'email' => '[email protected]', ]); Note: $fillable/$guarded properties 11 — Twitter/GitHub: @cmgmyr

Slide 12

Slide 12 text

UPDATING $user = User::find(1); $user->email = '[email protected]'; $user->save(); 12 — Twitter/GitHub: @cmgmyr

Slide 13

Slide 13 text

UPDATING $user = User::find(1); $user->update([ 'email' => '[email protected]', ]); Note: $fillable/$guarded properties 13 — Twitter/GitHub: @cmgmyr

Slide 14

Slide 14 text

UPDATING $user = User::find(1); $user->fill([ 'email' => '[email protected]', ]); $user->save(); Note: $fillable/$guarded properties 14 — Twitter/GitHub: @cmgmyr

Slide 15

Slide 15 text

DELETING $user = User::find(1); $user->delete(); 15 — Twitter/GitHub: @cmgmyr

Slide 16

Slide 16 text

DELETING User::destroy(1); User::destroy([1, 2, 3]); User::destroy(1, 2, 3); 16 — Twitter/GitHub: @cmgmyr

Slide 17

Slide 17 text

"OR" HELPER METHODS User::findOrFail(1); $user->saveOrFail(); // same as save(), but uses transaction User::firstOrCreate([ /* attributes */]); User::updateOrInsert([/* attributes to search */], [/* attributes to update */]); 17 — Twitter/GitHub: @cmgmyr

Slide 18

Slide 18 text

QUERYING

Slide 19

Slide 19 text

QUERYING $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(); 19 — Twitter/GitHub: @cmgmyr

Slide 20

Slide 20 text

CHUNKING User::chunk(50, function ($users) { foreach ($users as $user) { // } }); 20 — Twitter/GitHub: @cmgmyr

Slide 21

Slide 21 text

COLLECTIONS For Eloquent methods like all() and get() which retrieve multiple results, an instance of Illuminate\Database\Eloquent\Collection will be returned. $admins = $users->filter(function ($user) { return $user->is_admin; }); 21 — Twitter/GitHub: @cmgmyr

Slide 22

Slide 22 text

RAW QUERY METHODS 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(); 22 — Twitter/GitHub: @cmgmyr

Slide 23

Slide 23 text

RELATIONSHIPS

Slide 24

Slide 24 text

RELATIONSHIPS 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 24 — Twitter/GitHub: @cmgmyr

Slide 25

Slide 25 text

RELATIONSHIPS class Video extends Model { public function comments() { return $this->hasMany(Comment::class); } } $video = Video::find(1); foreach ($video->comments as $comment) { // $comment->body } 25 — Twitter/GitHub: @cmgmyr

Slide 26

Slide 26 text

RELATIONSHIPS 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 } 26 — Twitter/GitHub: @cmgmyr

Slide 27

Slide 27 text

DEFAULT CONDITIONS AND ORDERING class Video extends Model { public function comments() { return $this->hasMany(Comment::class) ->where('approved', true) ->latest(); } } 27 — Twitter/GitHub: @cmgmyr

Slide 28

Slide 28 text

DEFAULT CONDITIONS AND ORDERING class Video extends Model { public function comments() { return $this->hasMany(Comment::class); } public function publicComments() { return $this->comments() ->where('approved', true) ->latest(); } } 28 — Twitter/GitHub: @cmgmyr

Slide 29

Slide 29 text

DEFAULT MODELS Default models can be used with belongsTo(), hasOne(), and morphOne() relationships. 29 — Twitter/GitHub: @cmgmyr

Slide 30

Slide 30 text

DEFAULT MODELS {{ $post->author->name }} // error if author not found class Post extends Model { public function author() { return $this->belongsTo(User::class); } } 30 — Twitter/GitHub: @cmgmyr

Slide 31

Slide 31 text

DEFAULT MODELS {{ $post->author->name ?? '' }} // meh class Post extends Model { public function author() { return $this->belongsTo(User::class); } } 31 — Twitter/GitHub: @cmgmyr

Slide 32

Slide 32 text

DEFAULT MODELS {{ $post->author->name }} // better! class Post extends Model { public function author() { return $this->belongsTo(User::class)->withDefault(); } } 32 — Twitter/GitHub: @cmgmyr

Slide 33

Slide 33 text

DEFAULT MODELS class Post extends Model { public function author() { return $this->belongsTo(User::class)->withDefault([ 'name' => 'Guest Author', ]); } } 33 — Twitter/GitHub: @cmgmyr

Slide 34

Slide 34 text

EVENTS The retrieved event will fire when an existing model is retrieved from the database. When a new model is saved for the first time, the creating and created events will fire. If a model already existed in the database and the save() method is called, the updating / updated events will fire. However, in both cases, the saving / saved events will fire. https://laravel.com/docs/5.6/eloquent#events 34 — Twitter/GitHub: @cmgmyr

Slide 35

Slide 35 text

EVENTS class User extends Model { protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ]; } 35 — Twitter/GitHub: @cmgmyr

Slide 36

Slide 36 text

OBSERVERS php artisan make:observer UserObserver -- model=User class ModelObserverServiceProvider extends ServiceProvider { public function boot() { User::observe(UserObserver::class); } } 36 — Twitter/GitHub: @cmgmyr

Slide 37

Slide 37 text

OBSERVERS class UserObserver { public function created(User $user) { } public function updated(User $user) { } public function deleted(User $user) { } } 37 — Twitter/GitHub: @cmgmyr

Slide 38

Slide 38 text

boot() METHOD class Post extends Model { public static function boot() { parent::boot(); self::creating(function ($model) { $model->uuid = (string) Uuid::generate(); }); } } 38 — Twitter/GitHub: @cmgmyr

Slide 39

Slide 39 text

BOOTABLE TRAIT 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 } 39 — Twitter/GitHub: @cmgmyr

Slide 40

Slide 40 text

HELPER METHODS

Slide 41

Slide 41 text

INCREMENTS AND DECREMENTS $post = Post::find(1); $post->stars++; $post->save(); $post->stars--; $post->save(); 41 — Twitter/GitHub: @cmgmyr

Slide 42

Slide 42 text

INCREMENTS AND DECREMENTS $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 42 — Twitter/GitHub: @cmgmyr

Slide 43

Slide 43 text

AGGREGATES $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'); 43 — Twitter/GitHub: @cmgmyr

Slide 44

Slide 44 text

CHECK IF RECORDS EXIST Instead of count(), you could use... User::where('username', 'cmgmyr')->exists(); User::where('username', 'cmgmyr')->doesntExist(); 44 — Twitter/GitHub: @cmgmyr

Slide 45

Slide 45 text

MODEL STATE $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; 45 — Twitter/GitHub: @cmgmyr

Slide 46

Slide 46 text

"MAGIC" WHERE() $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(); 46 — Twitter/GitHub: @cmgmyr

Slide 47

Slide 47 text

SUPER "MAGIC" WHERE() User::whereTypeAndStatus('admin', 'active')->get(); User::whereTypeOrStatus('admin', 'active')->get(); https://twitter.com/themsaid/status/ 1029731544942952448 47 — Twitter/GitHub: @cmgmyr

Slide 48

Slide 48 text

DATES 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')); 48 — Twitter/GitHub: @cmgmyr

Slide 49

Slide 49 text

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')); } 49 — Twitter/GitHub: @cmgmyr

Slide 50

Slide 50 text

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')); }); 50 — Twitter/GitHub: @cmgmyr

Slide 51

Slide 51 text

replicate() A MODEL $invoice = Invoice::find(1); $newInvoice = $invoice->replicate(); $newInvoice->save(); 51 — Twitter/GitHub: @cmgmyr

Slide 52

Slide 52 text

PAGINATION // 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() }} 52 — Twitter/GitHub: @cmgmyr

Slide 53

Slide 53 text

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 } ] } 53 — Twitter/GitHub: @cmgmyr

Slide 54

Slide 54 text

MODEL PROPERTIES 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! 54 — Twitter/GitHub: @cmgmyr

Slide 55

Slide 55 text

OVERRIDING updated_at $product = Product::find(1); $product->updated_at = '2020-01-01 10:00:00'; $product->save(['timestamps' => false]); 55 — Twitter/GitHub: @cmgmyr

Slide 56

Slide 56 text

PRIMARY KEY METHODS $video = Video::find(1); $video->getKeyName(); // 'id' $video->getKeyType(); // 'int' $video->getKey(); // 1 56 — Twitter/GitHub: @cmgmyr

Slide 57

Slide 57 text

ACCESSORS/MUTATORS class User extends Model { public function setFirstNameAttribute($value) { $this->attributes['first_name'] = strtolower($value); } public function setLastNameAttribute($value) { $this->attributes['last_name'] = strtolower($value); } } 57 — Twitter/GitHub: @cmgmyr

Slide 58

Slide 58 text

ACCESSORS/MUTATORS 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}"; } } 58 — Twitter/GitHub: @cmgmyr

Slide 59

Slide 59 text

ACCESSORS/MUTATORS $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' 59 — Twitter/GitHub: @cmgmyr

Slide 60

Slide 60 text

TO ARRAY/JSON $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. 60 — Twitter/GitHub: @cmgmyr

Slide 61

Slide 61 text

APPENDING VALUES TO 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(); 61 — Twitter/GitHub: @cmgmyr

Slide 62

Slide 62 text

LOCAL SCOPES $posts = Post::whereNotNull('published_at') ->where('published_at', '<=', Carbon::now()) ->latest('published_at') ->get(); 62 — Twitter/GitHub: @cmgmyr

Slide 63

Slide 63 text

LOCAL SCOPES class Post extends Model { public function scopePublished($query) { return $query->whereNotNull('published_at') ->where('published_at', '<=', Carbon::now()) ->latest('published_at'); } } 63 — Twitter/GitHub: @cmgmyr

Slide 64

Slide 64 text

LOCAL SCOPES $posts = Post::published()->get(); 64 — Twitter/GitHub: @cmgmyr

Slide 65

Slide 65 text

SINGLE TABLE INHERITANCE

Slide 66

Slide 66 text

SINGLE TABLE INHERITANCE $admins = User::where('is_admin', true)->get(); $customers = User::where('is_admin', false)->get(); 66 — Twitter/GitHub: @cmgmyr

Slide 67

Slide 67 text

SINGLE TABLE INHERITANCE 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(); 67 — Twitter/GitHub: @cmgmyr

Slide 68

Slide 68 text

SINGLE TABLE INHERITANCE class Admin extends User { protected static function boot() { parent::boot(); static::addGlobalScope(function ($query) { $query->where('is_admin', true); }); } } 68 — Twitter/GitHub: @cmgmyr

Slide 69

Slide 69 text

SINGLE TABLE INHERITANCE class Customer extends User { protected static function boot() { parent::boot(); static::addGlobalScope(function ($query) { $query->where('is_admin', false); }); } } 69 — Twitter/GitHub: @cmgmyr

Slide 70

Slide 70 text

SINGLE TABLE INHERITANCE $admins = Admin::get(); $customers = Customer::get(); 70 — Twitter/GitHub: @cmgmyr

Slide 71

Slide 71 text

SINGLE TABLE INHERITANCE Read more: > https://twitter.com/cmgmyr/status/ 885204646498893824 > https://tighten.co/blog/extending-models-in- eloquent 71 — Twitter/GitHub: @cmgmyr

Slide 72

Slide 72 text

DEFAULT MODEL DATA

Slide 73

Slide 73 text

DEFAULT MODEL DATA 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(); }); 73 — Twitter/GitHub: @cmgmyr

Slide 74

Slide 74 text

DEFAULT MODEL DATA class User extends Model { protected $fillable = [ 'name', 'email', 'password', 'role' ]; } 74 — Twitter/GitHub: @cmgmyr

Slide 75

Slide 75 text

DEFAULT MODEL DATA $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' 75 — Twitter/GitHub: @cmgmyr

Slide 76

Slide 76 text

DEFAULT MODEL DATA 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(); }); 76 — Twitter/GitHub: @cmgmyr

Slide 77

Slide 77 text

DEFAULT MODEL DATA Set $attributes class User extends Model { protected $fillable = [ 'name', 'email', 'password', 'role' ]; protected $attributes = [ 'role' => 'user', ]; } 77 — Twitter/GitHub: @cmgmyr

Slide 78

Slide 78 text

DEFAULT MODEL DATA $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' 78 — Twitter/GitHub: @cmgmyr

Slide 79

Slide 79 text

DEFAULT MODEL DATA $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' 79 — Twitter/GitHub: @cmgmyr

Slide 80

Slide 80 text

DEFAULT MODELS Remember our previous example? class Post extends Model { public function author() { return $this->belongsTo(User::class)->withDefault([ 'name' => 'Guest Author', ]); } } 80 — Twitter/GitHub: @cmgmyr

Slide 81

Slide 81 text

DEFAULT MODELS 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(); } } 81 — Twitter/GitHub: @cmgmyr

Slide 82

Slide 82 text

DEFAULT MODEL DATA class User extends Model { protected $fillable = [ 'name', 'email', 'password', 'role' ]; protected $attributes = [ 'name' => 'Guest Author', 'role' => 'user', ]; } 82 — Twitter/GitHub: @cmgmyr

Slide 83

Slide 83 text

DEFAULT MODEL DATA Watch Colin DeCarlo's - Keeping Eloquent Eloquent from Laracon US 2016 https://streamacon.com/video/laracon-us-2016/colin- decarlo-keeping-eloquent-eloquent 83 — Twitter/GitHub: @cmgmyr

Slide 84

Slide 84 text

SUB-QUERIES $customers = Customer::with('company') ->orderByName() ->paginate(); Get latest interactions?

{{ $customer ->interactions() ->latest() ->first() ->created_at ->diffForHumans() }}

84 — Twitter/GitHub: @cmgmyr

Slide 85

Slide 85 text

SUB-QUERIES 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();

{{ $customer->last_interaction_date->diffForHumans() }}

85 — Twitter/GitHub: @cmgmyr

Slide 86

Slide 86 text

SUB-QUERIES Jonathan Reinink's Laracon 2018 Online Talk - Advanced Querying with Eloquent https://github.com/reinink/laracon2018 86 — Twitter/GitHub: @cmgmyr

Slide 87

Slide 87 text

RESOURCES > https://laravel.com/docs/5.6/eloquent > https://laravel-news.com/eloquent-tips-tricks > https://twitter.com/themsaid/status/1029731544942952448 > https://twitter.com/cmgmyr/status/885204646498893824 > https://tighten.co/blog/extending-models-in-eloquent > https://streamacon.com/video/laracon-us-2016/colin-decarlo-keeping- eloquent-eloquent > https://github.com/reinink/laracon2018 > https://eloquentbyexample.com 87 — Twitter/GitHub: @cmgmyr

Slide 88

Slide 88 text

THANK YOU! PLEASE SAY "HI" TWITTER.COM/CMGMYR GITHUB.COM/CMGMYR CHRISGMYR.COM