Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mabdullahsari's avatar

How to add observable events and use in an observer

TL;DR - How do I register custom observable events from a trait and observe those from another via an observer instance?

I created a global scope, called CancellingScope. Analogous to how soft deleting works, but it is intended for regular users instead of users with elevated access.

I need to be able to listen to the cancelled event via a model observer to remove it from the Elastic Search index because I do not want people to be able to search for cancelled entities.

I examined the HasEvents trait and it exposes a class method called addObservableEvents. The problem is that this is a class method, not static and typically in a trait you register model observers statically, like so:

public static function bootTrait()
{
    static::observe(new ModelObserver);
}
  • I tried using a class method with the initialize prefix, but those methods are executed after the booting ones, so when it is way too late.

  • I looked at how soft deleting does this, but apparently it's just a hard-coded array and does not use the addObservableEvents method. I tried overloading the $observables property, with no avail (throws exception).

Basically, what I want to achieve:

  1. Add custom (observable) events via Cancels.php (trait)
  2. Register observer that listens to those events via Searchable.php (trait)

Could anyone help me out with this? Thanks in advance.

0 likes
8 replies
mabdullahsari's avatar

Thanks for the link. Unfortunately, it does not as those are things that I already knew.

My main problem is adding "custom" events to the $observables property of the HasEvents trait before the boot methods are run so that the event observer's methods are properly registered. It does not register "unknown" events so even if I have a cancelled function in an observer, it is straight up ignored.

mabdullahsari's avatar

I have tried the following:

protected $observables = [
    'cancelling',
    'cancelled',
    'uncancelling',
    'uncancelled',
];

But then, I receive the error below (which is logical): Illuminate\Database\Eloquent\Model and App\Models\Traits\Cancels define the same property ($observables) in the composition of App\Models\Entity. However, the definition differs and is considered incompatible.

I have two solutions, but neither of them make me happy:

  1. Defining the observable events in each model that is cancellable (not DRY)
  2. Overriding getObservableEvents in Cancels trait like so:
    public function getObservableEvents()
    {
        return array_merge($this->observables, [
            'retrieved', 'creating', 'created', 'updating', 'updated',
            'saving', 'saved', 'restoring', 'restored', 'replicating',
            'deleting', 'deleted', 'forceDeleted',
            'cancelling', 'cancelled', 'uncancelling', 'uncancelled',
        ]);
    }

Both of them are "meh" solutions, repetitive/hacky. There must be a proper way to populate the $observables property in the trait itself... (before the boot methods are run)

1 like
mabdullahsari's avatar

Bumping as this is still an issue. Currently working it around by overriding getObservableEvents, but it is hacky.

petercv's avatar

Hi @mabdullahsari. You can add a non-static function initialize to your trait which will be called from the constructor of model classes that use your trait. In that method you can call $this->addObservableEvents.

1 like
mabdullahsari's avatar

@petercv I don't know why you decided to gravedig, but I've explicitly stated the following...

I tried using a class method with the initialize prefix, but those methods are executed after the booting ones, so when it is way too late.

basher's avatar

@petercv is right. Adding it in initialize{TraitName} works. Example:

trait Archivable
{
    /**
     * Initialize Model Hook
     *
     * Adds our custom model events
     */
    public function initializeArchivable()
    {
        $this->addObservableEvents([
            'archiving',
            'archived',
            'unarchiving',
            'unarchived'
        ]);
        debug($this->observables);
    }

// etc

}
1 like

Please or to participate in this conversation.