Using Laravel Model Observers

66,135

Laravel Model Observers is a great feature. Observers are used to group event listeners for a model. Observers classes method names refer to the Eloquent event you want to listen for. These methods receive the model as their only argument. Laravel does not include a default directory for observers. Also, artisan command for generating observers is also not available by default.

Let’s implement Model Observers in a laravel project and understand through a simple example.

Starting the project

Create a new laravel project named Observables by running the following command:

laravel new Observables

Setup your database and add the credentials in .env file. We will be demonstrating observers through a simple product example. First create a Product model, controller and migration by running the following command:

php artisan make:model Product -mc

We have added m and c flags in the above command. They will also create migration and controller for the model respectively.

Now, update the schema in the migration file we created in the previous step. It will be named create_products_table.php.

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->text('description');
        $table->integer('price');
        $table->integer('quantity');
        $table->timestamps();
    });
}

Run the following command to migrate your database:

php artisan migrate

It will create products table. Now let’s set up our routes in routes/web.php file. There will be two routes for creating a product and storing a product. Here are the contents:

Route::get('/product', 'ProductController@create');
Route::post('/product', 'ProductController@store');

Creating view and backend logic

Now let’s create our view. Create a new view named create.blade.php in resources/views directory. Copy the contents below to the view.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Model Observer</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
    <div class="container" style="margin-top: 50px;">
        <div class="row">
            <div class="col-sm-10 offset-sm-1">
                @if($message = session()->pull('message'))
                    <div class="alert alert-success">{{ $message }}</div>
                @endif
                <form method="post" action="/product">
                    {{ csrf_field() }}
                    <div class="form-group">
                        <label for="name">Name:</label>
                        <input type="text" class="form-control" id="name" name="name" placeholder="Enter Product Name">
                    </div>
                    <div class="form-group">
                        <label for="description">Description</label>
                        <textarea name="description" id="description" class="form-control"
                                  placeholder="Enter Product Description"></textarea>
                    </div>
                    <div class="form-group">
                        <label for="price">Price</label>
                        <input type="number" class="form-control" id="price" name="price" placeholder="Enter Product Price">
                    </div>
                    <div class="form-group">
                        <label for="quantity">Quantity</label>
                        <input type="number" class="form-control" id="quantity" name="quantity"
                               placeholder="Enter Product Quantity">
                    </div>
                    <div class="form-group">
                        <button type="submit" class="btn btn-primary" name="submit">Submit</button>
                    </div>
                </form>
                @if(count($errors))
                    <ul class="alert alert-danger well">
                        @foreach($errors->all() as $error)
                            <li class="list-unstyled">{{ $error }}</li>
                        @endforeach
                    </ul>
                @endif
            </div>
        </div>



        @if(count($products))
            <hr class="dl-horizontal">

            <div class="row">
                <div class="col-sm-10 offset-sm-1">
                    <table class="table table-dark">
                        <thead>
                        <tr>
                            <th scope="col">#</th>
                            <th scope="col">Name</th>
                            <th scope="col">Description</th>
                            <th scope="col">Quantity</th>
                            <th scope="col">Price</th>
                        </tr>
                        </thead>
                        <tbody>
                        @foreach($products as $product)
                            <tr>
                                <th scope="row">{{ $loop->iteration }}</th>
                                <td>{{ $product->name }}</td>
                                <td>{{ $product->description }}</td>
                                <td>{{ $product->quantity }}</td>
                                <td>{{ $product->price }}</td>
                            </tr>
                        @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        @endif
    </div>
</body>
</html>

In this view, we are simply creating a form for creating a product using bootstrap 4 style. Link to the bootstrap css file is added in the head section. After that, we are adding a table that will show all of the available products. At the top, there is an alert box that will show if the product creation was successful.

Now, let’s set up our controller. Copy the contents below to ProductController.php file.

<?php

namespace App\Http\Controllers;

use App\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    public function create()
    {
        $products = Product::all();
        return view('create', compact('products'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required',
            'description' => 'required',
            'price' => 'required|numeric',
            'quantity' => 'required|numeric'
        ]);

        $product = new Product();
        $product->name = $request->name;
        $product->description = $request->description;
        $product->price = $request->price;
        $product->quantity = $request->quantity;
        $product->save();

        return redirect('/product')->with('message', 'Product created successfully');
    }
}

In the create method, we are simply getting all of the product models available and returning the view.

Next, in the store method, first the data is validated and then a new product is created and saved in the database. Then we redirect back to the product page with the success message that is shown at the top of the page.

Using Model Observers

Now let’s say that the price of the product needs to be updated. We have to add some tax to the product price. Also, for any product created with quantity more than 10, we will give them 50% compensation in the tax. But we do not want to add this logic to our controller. We can use model events that are fired automatically by laravel when the new record is created, updated or deleted.

This is the list of all of the events, eloquent model fired that we can hook into

  • retrieved
  • creating
  • created
  • updating
  • updated
  • saving
  • saved
  • deleting
  • deleted
  • restoring
  • restored

You can find more eloquent events in the official laravel docs. Now let’s write our logic to boot method present in app/Provides/AppServiceProvider.php.

public function boot()
{
    \App\Product::creating(function ($model) {
        $tax = .20;

        if ($model->quantity < 10) { $model->price += $model->price * $tax;
        } else if ($model->quantity >= 10) {
            $model->price += $model->price * ($tax / 2);
        }
    });
}

Now, at the time of creation, we are overriding the value of the price based on their quantity. So, here we have used model event creating to do the job.

We can also create a dedicated class that provides eloquent event methods for a specific model.

Creating dedicated model observers

Adding all of the observer code in AppServiceProvider is not a good choice. We can create dedicated classes for each model. First, we have to create a service provider. Run the following command to create it:

php artisan make:provider ProductModelServiceProvider

Register the service provider by adding the following entry to the providers array in config/app.php file:

 App\Providers\ProductModelServiceProvider::class,

Observers cannot be created through command line by default. So let’s create an Observer directory inside the app folder. Within the Observer folder, create ProductObserver.php file with the following contents:

<?php

namespace App\Observer;

use App\Product;

class ProductObserver
{
    public function creating(Product $product)
    {
        $tax = .20;

        if ($product->quantity < 10) {
            $product->price += $product->price * $tax;
        } else if ($product->quantity >= 10) {
            $product->price += $product->price * ($tax / 2);
        }
    }
}

Go, to ProductModelServiceProvider.php file and add the following in the boot method. It will tell laravel that we are observing Product model on the ProductObserver class.

\App\Product::observe(\App\Observer\ProductObserver::class);

Now remove the creating observer we added in AppServiceProvider.php file. Autoloading of the files may not work properly because we created a folder and added our observable file. Run the following two commands one by one to make sure that you won’t get any errors:

# Autoloading of files
composer dump

# Configure the cache
php artisan config:cache

Now test the application and you will find the price altered on the basis of quantity.

This is how we can use Model Observers in Laravel application. They are very useful in refactoring our code and working with the cache.

Here’s how the application looks:

Observer in Laravel

You can find the entire source code for this simple application here

You might also like
Comments