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. 2.
  2. 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
  3. 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
  4. 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
  5. 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
  6. 9.
  7. 10.

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

    'cmgmyr@gmail.com'; $user->save(); 10 — Twitter/GitHub: @cmgmyr
  8. 11.

    CREATING $user = User::create([ 'first_name' => 'Chris', 'email' => 'cmgmyr@gmail.com',

    ]); Note: $fillable/$guarded properties 11 — Twitter/GitHub: @cmgmyr
  9. 13.

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

    $fillable/$guarded properties 13 — Twitter/GitHub: @cmgmyr
  10. 14.

    UPDATING $user = User::find(1); $user->fill([ 'email' => 'me@chrisgmyr.com', ]); $user->save();

    Note: $fillable/$guarded properties 14 — Twitter/GitHub: @cmgmyr
  11. 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
  12. 18.
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 29.

    DEFAULT MODELS Default models can be used with belongsTo(), hasOne(),

    and morphOne() relationships. 29 — Twitter/GitHub: @cmgmyr
  22. 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
  23. 31.

    DEFAULT MODELS {{ $post->author->name ?? '' }} // meh class

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

    DEFAULT MODELS {{ $post->author->name }} // better! class Post extends

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

    DEFAULT MODELS class Post extends Model { public function author()

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

    EVENTS class User extends Model { protected $dispatchesEvents = [

    'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ]; } 35 — Twitter/GitHub: @cmgmyr
  28. 36.

    OBSERVERS php artisan make:observer UserObserver -- model=User class ModelObserverServiceProvider extends

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

    OBSERVERS class UserObserver { public function created(User $user) { }

    public function updated(User $user) { } public function deleted(User $user) { } } 37 — Twitter/GitHub: @cmgmyr
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 56.

    PRIMARY KEY METHODS $video = Video::find(1); $video->getKeyName(); // 'id' $video->getKeyType();

    // 'int' $video->getKey(); // 1 56 — Twitter/GitHub: @cmgmyr
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 74.

    DEFAULT MODEL DATA class User extends Model { protected $fillable

    = [ 'name', 'email', 'password', 'role' ]; } 74 — Twitter/GitHub: @cmgmyr
  54. 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
  55. 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
  56. 77.

    DEFAULT MODEL DATA Set $attributes class User extends Model {

    protected $fillable = [ 'name', 'email', 'password', 'role' ]; protected $attributes = [ 'role' => 'user', ]; } 77 — Twitter/GitHub: @cmgmyr
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 86.

    SUB-QUERIES Jonathan Reinink's Laracon 2018 Online Talk - Advanced Querying

    with Eloquent https://github.com/reinink/laracon2018 86 — Twitter/GitHub: @cmgmyr
  66. 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