Pro Yearly is on sale from $80 to $50! »

Tips, Tricks, and Good Practices with Laravel's Eloquent

Tips, Tricks, and Good Practices with Laravel's Eloquent

6834447bdbca70cd2390adc690c3b4f1?s=128

Chris Gmyr

August 16, 2018
Tweet

Transcript

  1. TIPS, TRICKS, AND GOOD PRACTICES WITH LARAVEL'S ELOQUENT PRESENTED BY

    CHRIS GMYR
  2. None
  3. 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
  4. 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
  5. THE BASICS

  6. A MODEL class Post extends Model { // look Ma,

    no code! } - id - title - created_at - updated_at $post = Post::find(1); 6 — Twitter/GitHub: @cmgmyr
  7. ARTISAN GOODIES php artisan make:model Product 7 — Twitter/GitHub: @cmgmyr

  8. 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
  9. CRUDDY

  10. CREATING $user = new User(); $user->first_name = 'Chris'; $user->email =

    'cmgmyr@gmail.com'; $user->save(); 10 — Twitter/GitHub: @cmgmyr
  11. CREATING $user = User::create([ 'first_name' => 'Chris', 'email' => 'cmgmyr@gmail.com',

    ]); Note: $fillable/$guarded properties 11 — Twitter/GitHub: @cmgmyr
  12. UPDATING $user = User::find(1); $user->email = 'me@chrisgmyr.com'; $user->save(); 12 —

    Twitter/GitHub: @cmgmyr
  13. UPDATING $user = User::find(1); $user->update([ 'email' => 'me@chrisgmyr.com', ]); Note:

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

    Note: $fillable/$guarded properties 14 — Twitter/GitHub: @cmgmyr
  15. DELETING $user = User::find(1); $user->delete(); 15 — Twitter/GitHub: @cmgmyr

  16. DELETING User::destroy(1); User::destroy([1, 2, 3]); User::destroy(1, 2, 3); 16 —

    Twitter/GitHub: @cmgmyr
  17. "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
  18. QUERYING

  19. 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
  20. CHUNKING User::chunk(50, function ($users) { foreach ($users as $user) {

    // } }); 20 — Twitter/GitHub: @cmgmyr
  21. 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
  22. 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
  23. RELATIONSHIPS

  24. 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
  25. 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
  26. 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
  27. DEFAULT CONDITIONS AND ORDERING class Video extends Model { public

    function comments() { return $this->hasMany(Comment::class) ->where('approved', true) ->latest(); } } 27 — Twitter/GitHub: @cmgmyr
  28. 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
  29. DEFAULT MODELS Default models can be used with belongsTo(), hasOne(),

    and morphOne() relationships. 29 — Twitter/GitHub: @cmgmyr
  30. 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
  31. DEFAULT MODELS {{ $post->author->name ?? '' }} // meh class

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

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

    { return $this->belongsTo(User::class)->withDefault([ 'name' => 'Guest Author', ]); } } 33 — Twitter/GitHub: @cmgmyr
  34. 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
  35. EVENTS class User extends Model { protected $dispatchesEvents = [

    'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ]; } 35 — Twitter/GitHub: @cmgmyr
  36. OBSERVERS php artisan make:observer UserObserver -- model=User class ModelObserverServiceProvider extends

    ServiceProvider { public function boot() { User::observe(UserObserver::class); } } 36 — Twitter/GitHub: @cmgmyr
  37. OBSERVERS class UserObserver { public function created(User $user) { }

    public function updated(User $user) { } public function deleted(User $user) { } } 37 — Twitter/GitHub: @cmgmyr
  38. 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
  39. 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
  40. HELPER METHODS

  41. INCREMENTS AND DECREMENTS $post = Post::find(1); $post->stars++; $post->save(); $post->stars--; $post->save();

    41 — Twitter/GitHub: @cmgmyr
  42. 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
  43. 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
  44. CHECK IF RECORDS EXIST Instead of count(), you could use...

    User::where('username', 'cmgmyr')->exists(); User::where('username', 'cmgmyr')->doesntExist(); 44 — Twitter/GitHub: @cmgmyr
  45. 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
  46. "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
  47. SUPER "MAGIC" WHERE() User::whereTypeAndStatus('admin', 'active')->get(); User::whereTypeOrStatus('admin', 'active')->get(); https://twitter.com/themsaid/status/ 1029731544942952448 47

    — Twitter/GitHub: @cmgmyr
  48. 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
  49. 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
  50. 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
  51. replicate() A MODEL $invoice = Invoice::find(1); $newInvoice = $invoice->replicate(); $newInvoice->save();

    51 — Twitter/GitHub: @cmgmyr
  52. 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
  53. 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
  54. 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
  55. OVERRIDING updated_at $product = Product::find(1); $product->updated_at = '2020-01-01 10:00:00'; $product->save(['timestamps'

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

    // 'int' $video->getKey(); // 1 56 — Twitter/GitHub: @cmgmyr
  57. 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
  58. 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
  59. ACCESSORS/MUTATORS $user = User::create([ 'first_name' => 'Chris', // chris 'last_name'

    => 'Gmyr', // gmyr 'email' => 'cmgmyr@gmail.com', ]); $user->first_name; // Chris $user->last_name; // Gmyr $user->email; // instance of Email $user->full_name; // 'Chris Gmyr' 59 — Twitter/GitHub: @cmgmyr
  60. 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
  61. 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
  62. LOCAL SCOPES $posts = Post::whereNotNull('published_at') ->where('published_at', '<=', Carbon::now()) ->latest('published_at') ->get();

    62 — Twitter/GitHub: @cmgmyr
  63. 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
  64. LOCAL SCOPES $posts = Post::published()->get(); 64 — Twitter/GitHub: @cmgmyr

  65. SINGLE TABLE INHERITANCE

  66. SINGLE TABLE INHERITANCE $admins = User::where('is_admin', true)->get(); $customers = User::where('is_admin',

    false)->get(); 66 — Twitter/GitHub: @cmgmyr
  67. 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
  68. 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
  69. 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
  70. SINGLE TABLE INHERITANCE $admins = Admin::get(); $customers = Customer::get(); 70

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

    eloquent 71 — Twitter/GitHub: @cmgmyr
  72. DEFAULT MODEL DATA

  73. 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
  74. DEFAULT MODEL DATA class User extends Model { protected $fillable

    = [ 'name', 'email', 'password', 'role' ]; } 74 — Twitter/GitHub: @cmgmyr
  75. DEFAULT MODEL DATA $user = new User(); $user->name = 'Chris';

    $user->email = 'cmgmyr@gmail.com'; $user->password = Hash::make('p@ssw0rd'); // $user->role is currently NULL $user->save(); $user->role; // 'user' 75 — Twitter/GitHub: @cmgmyr
  76. 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
  77. DEFAULT MODEL DATA Set $attributes class User extends Model {

    protected $fillable = [ 'name', 'email', 'password', 'role' ]; protected $attributes = [ 'role' => 'user', ]; } 77 — Twitter/GitHub: @cmgmyr
  78. DEFAULT MODEL DATA $user = new User(); $user->name = 'Chris';

    $user->email = 'cmgmyr@gmail.com'; $user->password = Hash::make('p@ssw0rd'); // $user->role is currently 'user'! $user->save(); $user->role; // 'user' 78 — Twitter/GitHub: @cmgmyr
  79. DEFAULT MODEL DATA $user = new User(); $user->name = 'Chris';

    $user->email = 'cmgmyr@gmail.com'; $user->password = Hash::make('p@ssw0rd'); $user->role = 'admin'; // can override default $user->save(); $user->role; // 'admin' 79 — Twitter/GitHub: @cmgmyr
  80. 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
  81. 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
  82. DEFAULT MODEL DATA class User extends Model { protected $fillable

    = [ 'name', 'email', 'password', 'role' ]; protected $attributes = [ 'name' => 'Guest Author', 'role' => 'user', ]; } 82 — Twitter/GitHub: @cmgmyr
  83. 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
  84. SUB-QUERIES $customers = Customer::with('company') ->orderByName() ->paginate(); Get latest interactions? <p>{{

    $customer ->interactions() ->latest() ->first() ->created_at ->diffForHumans() }}</p> 84 — Twitter/GitHub: @cmgmyr
  85. 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(); <p>{{ $customer->last_interaction_date->diffForHumans() }}</p> 85 — Twitter/GitHub: @cmgmyr
  86. SUB-QUERIES Jonathan Reinink's Laracon 2018 Online Talk - Advanced Querying

    with Eloquent https://github.com/reinink/laracon2018 86 — Twitter/GitHub: @cmgmyr
  87. 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
  88. THANK YOU! PLEASE SAY "HI" TWITTER.COM/CMGMYR GITHUB.COM/CMGMYR CHRISGMYR.COM