How to Mutate Eloquent Model Attributes

  • How to Mutate Eloquent Model Attributes

    Sharing is Caring... Show some love :)

    This article will explain the different ways to automatically manipulate and mutate eloquent model attributes while accessing or retrieving them. 

    There are many times when it’s necessary to do computations or add extra attributes based on existing attributes on an eloquent model. 

    An excellent example of this could be concatenating the first name (first_name) and last name (last_name) of the user to form a new computed attribute called name that didn’t exist on our model in the first place. 

    Another example could be prepending the host/app URL that points to stored files in our database during retrieval.

    There are several ways to achieve this, and we’ll discuss them along with their quirks in this article.

    Before you dive in, check out the ultimate guide to Laravel 2021 to stay up-to-date.

    Using an Accessor

    This accessor is perhaps the most popular way of mutating a model’s attribute. Laravel allows you to define unique methods on the Eloquent model that describe how you should mutate attributes during retrieval. 

    Code Example

    This is the syntax for using accessors in Laravel:

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class User extends Model
    {
        /**
         * Get the user's first name.
         *
         * @param  string  $value
         * @return string
         */
        public function getFirstNameAttribute($value)
        {
            return ucfirst($value);
        }
      
        /**
         * Get the user's phone number by combining the country code with the actual number.
         *
         * @return string
         */
        public function getPhoneNumberAttribute() 
        {
          return $this->country_code . $this->phone;  
        }
    }

    The code above contains two accessors. The User model maps to a user’s table with the first_name, country_code, and phone columns. The first function defines what would happen each time $user→first_name is called. 

    The second function creates a new attribute that didn’t exist on the user’s table. Each time the phone_number attribute is accessed on a User instance, Eloquent will combine the country code and phone to form the phone number. 

    Here’s an example of the accessors being used in actual code:

    <?php
    
    namespace App\Http\Controllers\Employer\Dashboard;
    
    use App\Http\Controllers\Controller;
    
    use App\Models\User;
    
    class HomeController extends Controller
    {
      public function getUser($id) {
        $user = User::find($id);
        $user->first_name; // The accessor has to be "accessed" for the function to work.
        $user->phone_number; // This would create this property and do the concatenation.
        return response(['data' => $user], 200);
      }
    }

    As seen in the example above, accessors have to be accessed for them to work. If they aren’t accessed, then it should return the standard object stored in the database.

    ALSO READ  Laravel Many to Many Relationships with CRUD Example

    Using Attribute Casting

    Casts are very similar to accessors. They allow you do the same kind of changes but with casts, you won’t need to add additional methods to your Eloquent model. 

    Laravel Casts itself is a huge topic and requires a separate article to explain in detail. You can check out the official documentation.

    Usually, Casts are used to convert your model attribute from one data type to another.  

    In order to use this, you have to define the $casts property (array) in your model and define attributes to be cast and the types they should be cast to.

    Laravel provides lots of useful types your attributes can be cast to, and they are: 

    • array
    • boolean
    • collection
    • date
    • datetime
    • decimal:<digits>
    • double
    • encrypted
    • encrypted:array
    • encrypted:collection
    • encrypted:object
    • float
    • integer
    • object
    • real
    • string
    • timestamp

    Code Example

    Here’s an example of a cast that always casts an attribute to Boolean.

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class User extends Model
    {
        /**
         * The attributes that should be cast.
         *
         * @var array
         */
        protected $casts = [
            'posts' => 'boolean',
        ];
    }

    Once we define a cast like this, we can access the value like usual, and Eloquent will cast it to the specified type.

    <?php
    
    namespace App\Http\Controllers\Employer\Dashboard;
    
    use App\Http\Controllers\Controller;
    
    use App\Models\User;
    
    class HomeController extends Controller
    {
      public function getUserPosts($id) {
        $user = User::find($id);
        $response = ['data' => $user];
        if($user->posts) {
          // Do something if user has posts 
        }
        return response($response, 200);
      }
    }

    To learn more about the different kinds of casts as well as how to create your custom casts. Check out cast and custom cast.

    Using Eloquent Events

    This is a third and less-known way of mutating model attributes. Using accessors is awesome, but they come with a severe limitation. Any attribute that needs to be mutated must first be “accessed” on the PHP side. This creates a problem when getting multiple records, for instance. 

    ALSO READ  Laravel 8 Multiple Role-based authentications

    Using our first example, if we wanted to get all users available in our database, send them to the frontend, and have our fields mutated. 

    We would have to do a map to access the properties first before sending them to the frontend, and this is an additional overhead on the server. Here’s how we would have to do it if we used a cast or an accessor:

    Code Example

    <?php
    
    namespace App\Http\Controllers\Employer\Dashboard;
    
    use App\Http\Controllers\Controller;
    
    use App\Models\User;
    
    class HomeController extends Controller
    {
      public function getUserPosts($id) {
        $users = User::all();
        $users->map(function ($user) {
          $user->first_name; // The accessor has to be "accessed" for the function to work.
          $user->phone_number; // This would create this property and do the concatenation.
        });
        return response(['data' => $users], 200);
      }
    }

    Before discussing how eloquent events help us solve this problem, let’s talk about what they are in the first place. 

    Every eloquent instance has a life cycle. 

    Eloquent emits different events as different actions are carried out on every model. It emits events when other actions happen with a model. These are the available events that eloquent emits:

    • Retrieved
    • Creating 
    • Created 
    • Updating 
    • Updated 
    • Saving 
    • Saved 
    • Deleting 
    • Deleted 
    • Restoring 
    • Restored 
    • Replicating

    These are the different actions that can happen to an eloquent model, and for each of these events, we can write event listeners that help us look into these events and carry out an action whenever any of these happen.

    See where we’re going now?

    Of all the events, the one of interest to us is the Retrieved event. Eloquent fires this event whenever a model is being retrieved, and we can hook into this event and mutate our record once it’s been retrieved.

    ALSO READ  Top 5 backend frameworks

    This means we won’t need to write any accessors, and we can manipulate the record directly in the event listener. 

    There are two ways to handle these events. The first is to write event listeners for this event. The second is to use a closure, and we’ll explore that in this example. 

    To achieve the same effect in the first method, we do this:

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class User extends Model
    {
        /**
         * The booted method of the model
         *
         * Define all attributes here like normal code
         *
         * @param  string  $value
         * @return string
         */
        protected static function booted() {
          static::retrieved(function($user) {
            $user->first_name = ucfirst($user->first_name);
            $user->phone_number = $user->country_code . $user->phone;
            return $user;
          });
        }
    }

    By using this method, we no longer have to “access” methods before they are computed. We use them as usual, and this function would always be called whenever a model is retrieved.

    <?php
    
    namespace App\Http\Controllers\Employer\Dashboard;
    
    use App\Http\Controllers\Controller;
    
    use App\Models\User;
    
    class HomeController extends Controller
    {
      public function getUser($id) {
        $user = User::find($id);
        // Mutations would happen automatically
        return response(['data' => $user], 200);
      }
      
      public function getUsers() {
        $users = User::all();
        return response(['data' => $users], 200);
      }
    }

    Conclusion

    In this article, we learned several ways to mutate eloquent model attributes and the properties of an Eloquent model.

    When building medium to large-scale applications, there will always be scenarios where we need such on-the-fly calculations. 

    As with everything else in programming, there’s seldom one way to do things. Before choosing any of these, consider your use-case, the trade-off, and keep building awesome things.

    If you found this article helpful, please share it with those you feel would need it and let me know.

    Catch you in the next one!

    Start Learning Backend Dev. Now

    Stop waiting and start learning! Get my 10 tips on teaching yourself backend development.

    Don't worry. I'll never, ever spam you!

    Sharing is caring :)

    Start Learning Now
    Learning for all. Savings for you. Courses from $11.99

    Comments