Understanding Laravel Model Observer, and Testing Using the Underlying Model
Tags: php, laravel, observer, testing
Published: 2023-12-27 02:06:32

In my journey with Laravel, one feature that consistently proves its value is the Model Observer. These observers are not just a neat organizational tool; they are a robust mechanism for reacting to various Eloquent model events – like creating, updating, or deleting models. Just recently, while working on a model with a conditional nullable column, Laravel Observers again demonstrated their efficiency. Let's delve into what Laravel Model Observers are and how you can leverage them effectively in your projects.

## What is a Laravel Model Observer?

In Laravel, a Model Observer is a class where you can group listener methods for various Eloquent model events. This pattern, derived from the Observer design pattern, is particularly useful for keeping your model-related logic clean and manageable. It lets you handle model events such as `creating`, `updating`, `deleting`, etc., in a dedicated class, ensuring that your model classes remain lean and focused on their primary responsibilities.

## Creating and Registering Observers:

Creating a Laravel Observer is straightforward. Use the artisan command to generate an observer class, ideally naming it after the model it observes. For instance, to create an observer for an Addon model, you would use:

```
php artisan make:observer AddonObserver --model=Addon
```

This command scaffolds an observer class in the `app\Observers` directory. Next, you need to register this observer with the model. This can be done in the boot method of the `App\Providers\EventServiceProvider` or any custom service provider like so:

```
use App\Models\Addon;
use App\Observers\AddonObserver;

public function boot(): void
{
    Addon::observe(AddonObserver::class);
}
```

Alternatively, you can list your observer within the `$observers` property of the `EventServiceProvider`:

```
protected $observers = [
    Addon::class => [AddonObserver::class],
];
```

## Practical Example: Ensuring Data Integrity

Consider an Addon model with varying types, where the `one-time` type does not require a `usable_count`. To ensure data integrity and prevent potential issues, we can implement a check in the observer's creating method:

```
namespace App\Observers;

use App\Models\Addon;
use Exception;

class AddonObserver
{
    public function creating(Addon $addon): void
    {
        if ($addon->type !== Addon::TYPE_ONE_TIME && is_null($addon->usable_count)) {
            throw new Exception(Addon::ADDON_TYPE_REQUIRES_USABLE_COUNT);
        }
    }
}
```
This code throws an exception if an `Addon` of a certain type is created without the necessary `usable_count`, thereby safeguarding the application from incorrect data entries.

## Testing Observers with Pest PHP

The robustness of Laravel applications often hinges on the effectiveness of our Observers. Testing these Observers thoroughly is crucial, and [Pest PHP](https://pestphp.com/) provides an expressive and elegant framework for this purpose. Let's explore a comprehensive test case that not only tests the Observer but also validates the behavior of the underlying model.

```
use App\Models\Addon;
use Exception;

test("ensuring addon observer integrity with model testing", function () {
    // Scenario: Creating an Addon without a required usable_count
    $this->expectException(Exception::class);
    $this->expectExceptionMessage(Addon::ADDON_TYPE_REQUIRES_USABLE_COUNT);

    Addon::factory()->create([
        'type' => Addon::TYPE_LIMITED, // This type requires usable_count
        'usable_count' => null         // Omitting usable_count
    ]);

    // Scenario: Successfully creating an Addon without needing a usable_count
    $addon = Addon::factory()->create([
        'type' => Addon::TYPE_ONE_TIME, // This type does not require usable_count
    ]);

    expect($addon)->toBeInstanceOf(Addon::class);
    expect($addon->usable_count)->toBeNull();
});
```
This test effectively checks the Observer's functionality by interacting directly with the Addon model, ensuring that the Observer enforces the necessary business rules.

## Conclusion:

Laravel Model Observers provide a structured way to handle model events and keep your models focused on their core responsibilities. Coupled with rigorous testing, Observers can significantly enhance the reliability and maintainability of your Laravel projects. You might want to visit the [official Laravel documentation](https://laravel.com/docs/10.x/eloquent#observers) to read more about what you can do with observers.