Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Chris Gmyr

August 16, 2018
Tweet

More Decks by Chris Gmyr

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. A MODEL class Post extends Model { // look Ma,

    no code! } - id - title - created_at - updated_at $post = Post::find(1); 6 — Twitter/GitHub: @cmgmyr
  4. 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
  5. CREATING $user = User::create([ 'first_name' => 'Chris', 'email' => '[email protected]',

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

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

    Note: $fillable/$guarded properties 14 — Twitter/GitHub: @cmgmyr
  8. "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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. DEFAULT CONDITIONS AND ORDERING class Video extends Model { public

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

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

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

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

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

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

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

    public function updated(User $user) { } public function deleted(User $user) { } } 37 — Twitter/GitHub: @cmgmyr
  26. 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
  27. 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
  28. 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
  29. 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
  30. CHECK IF RECORDS EXIST Instead of count(), you could use...

    User::where('username', 'cmgmyr')->exists(); User::where('username', 'cmgmyr')->doesntExist(); 44 — Twitter/GitHub: @cmgmyr
  31. 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
  32. "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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. PRIMARY KEY METHODS $video = Video::find(1); $video->getKeyName(); // 'id' $video->getKeyType();

    // 'int' $video->getKey(); // 1 56 — Twitter/GitHub: @cmgmyr
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. DEFAULT MODEL DATA class User extends Model { protected $fillable

    = [ 'name', 'email', 'password', 'role' ]; } 74 — Twitter/GitHub: @cmgmyr
  50. 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
  51. 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
  52. DEFAULT MODEL DATA Set $attributes class User extends Model {

    protected $fillable = [ 'name', 'email', 'password', 'role' ]; protected $attributes = [ 'role' => 'user', ]; } 77 — Twitter/GitHub: @cmgmyr
  53. 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
  54. 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
  55. 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
  56. 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
  57. DEFAULT MODEL DATA class User extends Model { protected $fillable

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

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

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