DEV Community

Cover image for Practical Guide to Cleaner Laravel Controllers: Harnessing the Power of Accessors and Mutators
Michael Menebraye
Michael Menebraye

Posted on

Practical Guide to Cleaner Laravel Controllers: Harnessing the Power of Accessors and Mutators

Laravel uses a Model-View-Controller architectural pattern and controllers play an important role. The main task of a controller in Laravel is to manage user http requests, render views and communicate with the application models for data modifications. When building any Laravel application, it is essential to adhere to best practices by implementing the MVC design pattern. Following this pattern ensures the maintainability and reusability of your code. For instance, the controller can be employed for writing business logic; however, this is generally not considered a best practice. Following best practices involves keeping controllers focused on handling http request and application flow, while business logic is better placed in the model or service for a cleaner and more organized codebase. Now, let's explore an actual case that demonstrates the functioning of controllers.

Practical Illustration of a Controller in Action

In the accounting industry, a Corporate Controller holds a pivotal managerial role, overseeing financial functions within an accounting firm. This position entails the management of departments like Financial Reporting, Budgeting and Forecasting, Compliance, and Treasury Management. The Corporate Controller is tasked with ensuring precise financial reporting, upholding internal controls, adhering to regulations, making strategic financial decisions, and providing leadership and supervision to teams within the finance division.

The role of a Corporate Controller in the accounting industry can be compared with that of a Laravel Controller. The controller in Laravel is an important part that controls the flow and request handling inside the application. It acts as a hub through which different functions are managed analogous to how the Corporate Controller manages financial aspects.

For example, while a Corporate Controller oversees departments such as Financial Reporting, Budgeting and Forecasting, and Compliance, a Laravel Controller handles different aspects of an application, including data retrieval and processing views rendering. The Laravel Controller, like its corporate counterpart, acts as a key guardian, ensuring data accuracy. Given the facts we've emphasized about maintaining a tidy controller, how can we ensure the cleanliness of our controllers? This can be achieved through the use of accessors and mutators.

Accessors

Accessors are also used to create fake attributes that do not belong to the database column. These fake attributes can be accessed as if they are stored columns in the database. Data obtained from the database can be manipulated using accessors. In object-oriented programming (OOP) in any language, accessors bear similarity to getters. Let us take a look at some possible situations where assessors can be applied to our applications.

Scenario 1:

Picture yourself working on a user registration system, where the goal is to showcase each user's full name in a specific format without persistently storing it in the database. To accomplish this, you can employ an accessor to create a fullname attribute by combining the first and last names, capitalizing the initial letter of each name. This fullname attribute exists only in memory and is not in the database.

Scenario 2:

You are building an API for frontend developers, and you want to display user information without exposing the exact user ID for security reasons. You can use accessors to hash the user ID before sending it to the front-end.

Naming conventions for accessors:

Accessors follow a naming convention that requires the creation of methods in the model to format attribute values upon retrieval from the database. This convention dictates using the get{AttributeName}Attribute method on the model, where {AttributeName} represents the name of the target attribute. For instance, if dealing with an attribute named first_name, the corresponding accessor method would be getFirstNameAttribute. Please note that attributes of a model are the fields or columns in the associated database table.

accessors Image

Mutators

Like accessors, mutators are also used for data manipulation. The main difference with Mutators is that the manipulated data is stored in a database. They can be compared to setters in object-oriented programming (OOP). We are going to consider some possible scenarios where mutators can be implemented.

Scenario 1:

Picture yourself developing a registration functionality for a web application, and your goal is to guarantee the security of user credentials by encrypting the password. You can use a mutator to hash the password before saving it to the database.

Scenario 2:

Imagine you are building a system where users are allowed to save a phone number containing a plus symbol (+) and you want to remove the plus symbol before storing the number in the database, you can use a mutator to implement this feature.

Naming conventions for mutators:

Mutators adhere to a naming convention that entails generating methods in the model to modify the attributes before they are stored in the database. The convention specifies using the set{AttributeName}Attribute method on the model, where {AttributeName} corresponds to the name of the targeted attribute. For instance, with an attribute named first_name, the mutator method would be setFirstNameAttribute.

mutators Image

Reasons for using accessors and mutators.

  • Maintenance: They make it easier to modify the behavior of certain attributes in one centralized location, reducing the risk of introducing errors while making updates.

  • Readability: Separating logic related to attribute manipulation from the main application logic improves the readability of the code.

  • Reusability: By encapsulating logic within them, you can reuse these methods across different parts of your application, promoting code reuse and avoiding duplicating similar logic.

  • Testing: They make it easier to unit test specific behavior associated with attribute retrieval or setting.

Prerequisites

  • Basic knowledge of getters and setters in object-oriented programming (OOP)

  • Laravel and PHP Basics

  • Mysql Database

  • Composer and PHP installed

Implementing Accessors and Mutators

Now that you have a solid understanding of accessors and mutators in Laravel, let's dive into some practical scenarios where these concepts can be implemented. We will explore step-by-step guides for implementing the few scenarios mentioned earlier:

Step 1: Creating and setting up a new laravel application

In your terminal, execute the following command to generate a new Laravel Project named example-app

composer create-project laravel/laravel example-app
Enter fullscreen mode Exit fullscreen mode

Next, open the example-app project and locate the .env file to set up your database preferences. Input the desired database name in the DB_DATABASE field and, if applicable, enter a password in the DB_PASSWORD field.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=*****
DB_USERNAME=root
DB_PASSWORD=****
Enter fullscreen mode Exit fullscreen mode

Go to the database directory, then navigate to the migrations folder, find the migration file suffix named _create_users_table.php, and open it.

users_table Image

Next, replace the up method with the code below

public function up(): void
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('first_name');
        $table->string('last_name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}
Enter fullscreen mode Exit fullscreen mode

Then run this command on your terminal

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

You should have these empty rows in your users table after running the migration command

users table migration

Step 2: Implementing Mutators

Go to app/models/ directory and locate the User.php model file. This is the location where we will implement all our business logic.

Implementing Mutators Image

Replace the existing code in this file with the provided code below.

<?php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
   protected $fillable = [
       'first_name',
       'last_name',
       'email',
       'password',
   ];
   protected $hidden = [
       'password',
       'remember_token',
   ];

   public function setPasswordAttribute($value)
   {
       $this->attributes['password'] = bcrypt($value);
   }
}

Enter fullscreen mode Exit fullscreen mode

The code illustrates the creation of a fillable property, hidden property, and a mutator method.

  • The $fillable property permits mass assignment solely for specified fields.

  • The $hidden property safeguards sensitive data by omitting it from public representations when the model is converted to an array or JSON.

  • The setPasswordAttribute method hashes user passwords before they are stored in the database.

Our primary focus is on the setPasswordAttribute method. We use Laravel's bcrypt function to encrypt the provided password value, setting the password attribute to the hashed password value before storing it in the database.

Create a user controller by executing this command on your terminal:

php artisan make:controller UserController
Enter fullscreen mode Exit fullscreen mode

Navigate to the app/Http/Controllers/ directory and open the UserController file.

Controllers directory

Replace the existing code in the UserController file with the provided code below.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;

class UserController extends Controller
{
  public function showCreateUserForm()
  {
      return view('create-user');
  }

  public function createUser(Request $request)
  {
      $user = User::create([
          'first_name' => $request->input('first_name'),
          'last_name' => $request->input('last_name'),
          'email' => $request->input('email'),
          'password' => $request->input('password'),
      ]);

      return redirect('/create-user')->with('success', 'User created successfully');
  }
}
Enter fullscreen mode Exit fullscreen mode

Go to resources/views/ directory and create a new file named create-user.blade.php

create-user Image

Copy and Paste the code provided below in create-user.blade.php

@if(session('success'))
  <div style="color:green;text-align:center;">
      {{ session('success') }}
  </div>
@endif

<form method="POST" action="{{ url('/create-user') }}">
  @csrf

  <label for="first name">First Name:</label>
  <input type="text" name="first_name" required>
  <br>

  <label for="last name">Last Name:</label>
  <input type="text" name="last_name" required>
  <br>

  <label for="email">Email:</label>
  <input type="email" name="email" required>
  <br>

  <label for="password">Password:</label>
  <input type="password" name="password" required>
  <br>

  <button type="submit">Create User</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Open the web.php in the routes directory and replace the existing code with the code provided below.

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;

Route::get('/', function () {
  return view('welcome');
});

Route::get('/create-user', [UserController::class, 'showCreateUserForm']);
Route::post('/create-user', [UserController::class, 'createUser']);
Enter fullscreen mode Exit fullscreen mode

Execute the command below to serve the application

php artisan serve
Enter fullscreen mode Exit fullscreen mode

Next, copy the base URL from the terminal and paste it into your browser. Then append the /create-user route to the base URL. Your view should resemble this.

localhost

Create a new user by entering the necessary information into the input fields. Keep in mind that no validation has been implemented in this application, so ensure accurate information is provided.

Open your database management tool and check the user table.

DB Image

You can observe that the user password in the password column has been hashed. It's worth noting that we chose not to implement password encryption in the controller, which is a common practice among developers. This method of using a mutator to hash the password helps maintain a clean and straightforward controller.

Step 3: Implementing Accessors

We won't create a new Laravel application to implement accessors; instead, we'll utilize the existing example-app used for implementing mutators. Let's proceed with implementing the accessors method. In this instance, we will create a username using the first name, last name, and id attribute.

Copy the code provided below and paste it into the user model, positioning the:

  • getUsernameAttribute: below the setPasswordAttribute method.
  • $appends: below or top of the protected properties.

The $appends is used to add extra information to the JSON output of your model without having to modify the database schema. It's useful for including manipulated or formatted data that you want to send to your frontend or other parts of your application.

protected $appends = ['username'];

public function getUsernameAttribute()
{
   $firstName = $this->attributes['first_name'];
   $lastName = $this->attributes['last_name'];
   $id = $this->attributes['id'];
   $username = $firstName . '-' . $lastName . '-' . $id;

   return strtolower($username);
}
Enter fullscreen mode Exit fullscreen mode

Next, navigate to the UserController, then copy the code provided below and paste the code below the CreateUser method. This method will retrieve all the users stored in the database.

public function getUserName()
{
   $users = User::all();
   return view('create-user', ['users' => $users]);
}
Enter fullscreen mode Exit fullscreen mode

Add the router method below in the web.php file located in the routes directory.

Route::get('/create-user', [UserController::class, getUserName]);
Enter fullscreen mode Exit fullscreen mode

Finally, copy the provided code below and paste it in the create-user.blade.php file located in the resources/views/ directory. Make sure to insert it below the closing HTML form tag.

<h1>All Users</h1>

<ul>
  @foreach ($users as $user)
      <li> <b>First Name:</b> {{ $user->first_name }} <br>
      <b>Last Name:</b> {{ $user->last_name }} <br>
      <b>User Name:</b> {{ $user->username }}</li>
  @endforeach
</ul>
Enter fullscreen mode Exit fullscreen mode

Go to your browser and refresh the page; you should observe that a new username that is not stored in the database is being displayed.

last Image

Conclusion

This practical guide has explored the art of creating cleaner Laravel controllers by leveraging the capabilities of accessors and mutators. By following best practices and incorporating these potent tools, you can substantially improve the quality of your codebase.

References

Top comments (0)