# Eloquent models (part 2)

  • In previous chapters, we discussed the basics about models:
    • we created a model for each database table
    • used the $fillable or $guarded properties to prevent mass assignment vulnerability
    • used the $hasMany and $belongsTo relations between the database tables
  • In this chapter, you will get an introduction to:
    • accessors and mutators
    • hide attributes from the database table
    • add additional properties
    • query scopes
  • For these examples, we work in the controller app/Http/Controllers/Admin/RecordController.php and the related view resources/views/admin/records/index.blade.php

do this first

  • Before starting this chapter, make sure you have configured the debug middleware

# Genres table

  • Let's start with querying the smallest table genres

# Get all genres

REMARK

The same result (retrieving all the genres) can be achieved with $genres = Genre::all()

TIP

  • Use the autocompletion of PhpStorm for automatic imports
    • Start typing 'Gen..'
    • Choose the proper class: Genre [App\Models] Genres: automatic import
    • Push Enter
  • By doing so, you do not have to add the import statement (use App\Models\Genre;) manually

# Order genres

  • Laravel's ORM allows chaining different Eloquent methods to fine-tune the query
  • One of the methods you probably always use is orderBy() (a -> z) or orderByDesc() (z -> a)

# Hide attributes

  • If we don't need the created_at and updated_at attributes in our JSON representation, we can hide them by adding these fields to the $hidden array in the Genre model
  • What if you want to hide these attributes for almost every query except for this one?
  • No problem, you can make hidden attributes back visible inside the controller by chaining the makeVisible()methode AFTER you get all the genres

TIP

If you want an attribute to be visible in most of your queries, except for one, leave the $hidden array in the model empty and chain the makeHidden() method inside the controller.
E.g. $genres = Genre::orderBy('name')->get()->makeHidden(['created_at', 'updated_at']);

REMARK

  • $hidden and makeHidden() only hides attributes from the JSON representation but you can still use the hidden attribute in a view!

# Accessors and mutators

  • An accessor transform the attribute after it has retrieved from database
  • A mutator transform the attribute before it is sent to database
  • In our genres table we want:
    • always store the name in lowercase letter (= mutator)
    • always show the name with the first letter in uppercase (= accessor)
  • IMPORTANT: the name of the method MUST BE the name of the attribute you want to manipulate!

# Genre with records

  • The model Genre has a relation with the model Record
  • You can include the records that belong to a genre with the with() method
    • The with() method takes a relation name as argument: with('records')
  • Also update the view to show all the genres with the records

TIP

Open a second browser tab and go to http://vinyl_shop.test/admin/records?json (opens new window) to see the JSON representation of the data

# Genres that has records

  • Most of the genres have no records
  • Use the has() method to filter on only the genres that have records
    • The has() method takes a relation name as argument: has('records')

# Records table

# Get all records with genre

  • The model Record has a relation with the model Genre
  • You can include the genre of a record with the with() method
    • The with() method takes a relation name as argument: with('genre')
  • Before we update the view, let us first inspect the JSON representation of the data at http://vinyl_shop.test/admin/records?json (opens new window)

# Additional attribute (genre_name)

  • In the view, we can select the genre name with $record->genre->name and that's fine but won't it be easier to add the genre name as an additional attribute to the record?
  • To add additional attributes to the JSON representation of the model
    • First, define an accessor for the attribute you want to add
      (The name of the accessor must be different from the attribute names in the database table)
    • Then, add the attribute to the $appends array in the model

# Additional attribute (price_euro)

  • The second additional attribute we're going to add is the price, formatted with 2 decimals and the Euro symbol (€)

# Additional attribute (cover)

  • As you may have noticed, there is no cover column in the records table
  • The image refers to the record's mb_id attribute and can be found in the covers folder.
    E.g. if the mb_id value is fcba15e2-3d1e-40b3-996c-be22450bda82, the path to the cover is
    /storage/covers/fcba15e2-3d1e-40b3-996c-be22450bda82.jpg
  • We use Laravels File System (opens new window) to:
    • check if the file exists
    • if the file exists, return the path to the file
    • else return the path to the dummy cover (no-cover.png)

Storage import

  • Storage (opens new window) is a Facade class from Laravel
  • Don't forget to add the correct import: use Illuminate\Support\Facades\Storage; or use Storage;

# Update the view

  • Add, for every record, a card with the fields: cover['url'], title, artist, genre_name, price_euro, and stock

# Query scope (price ≤ € 20)

  • Sometimes you want to filter the results of a query by a certain attribute value
  • This can be done with the where method inside the controller, but you can also do it in the model with local or global query scopes, so you don't have to write the same code twice
  • Local scopes allow you to define common sets of query constraints that you may easily re-use throughout your application, e.g. to filter out all users with the admin role
  • Let's create a filter for the records with a price less or equal to € 20
    • first, we filter the records inside the controller
    • then we refactor the controller and move the filter to the model
  • Let's create a global scope to filter the records with a price less or equal to $maxPrice
    • first, we create the scope
    • then we add it to the model
  • Local scopes always start with the prefix scope, followed by the name of the scope
    • e.g. the scope scopeMaxPrice($query, $price = 100) can be used inside the controller as:
      • maxPrice($maxPrice)
      • maxPrice() (if the default value of 100 is used)
    • the first parameter in the function is always the query
    • the second parameter (optional) is the value or values to filter by

IMPORTANT

  • Open the menu Laravel > Generate Helper Code to add the new scope for type hinting and auto-completion in PhpStorm
  • Repeat this step for every new scope you create
    Generate Helper Code

# Pagination

  • Because the list of records can be very long, it's better to show only a fraction of the records and add some pagination (opens new window) to the views
  • You can limit the number of records per page by replacing get() with paginate(x), where x is the number of records per view (e.g. 6)
  • Add a pagination navigation to the view (e.g. one before and another one after the loop) using
    $records->withQueryString()->links()

REMARKS

  • By default, the views rendered to display the pagination links are styled with Tailwind, but of course you can customize the pagination view (opens new window)
  • Laravel adds some extra attributes (current_page, first_page_url, ...) that eventually can be used inside the view

Extra attributes with pagination

# Fast model overview

  • Now that we added some extra attributes to the model, it's sometimes hard to remember all those attributes and the relations to other models
  • The artisan CLI has the great command php artisan model:show <Model> to generate a compact overview with a lot of information about the model (table name, fillable attributes, appended attributes, relations to other models, ...)

Inspecting database information requires the Doctrine DBAL (doctrine/dbal) package. Would you like to install it?

  • The first time you execute this artisan command, it might ask you to install the Doctrine DBAL package.
  • Choose y and press enter to continue
  • Re-execute the php artisan model:show <Model> command

# Exercise

  • Make the background color of the card red if the record is out of stock Exercise
Last Updated: 10/28/2022, 6:42:59 AM