Csatlakozz a Slack chatre
Szalai Barna / 1 éve

Fake Model Observer készítése

A dokumentáció részletesen tárgyalja a Model Observerek működését, amelyeket sok mindenre fel tudunk használni. A Observer egy olyan funkció, amely segítségével ha történik valami a modellünkkel, akkor meghívhatunk egyedi kódrészleteket. Alap esetben létrehozáskor, módosításkor és törléskor fut le egy esemény, amire “figyelni” tudunk, és a megfelelő kódot le tudjuk futtatni. De mi van akkor, ha pl. nekünk esedi modell eseményre van szükségünk, pl. egy cikk státusz állítására (1 – látszik, 0 – nem látszik az oldalon).

A cél: egy cikk státusz állításakor, a megfelelő cache tárak törlődjenek.

A probléma: a created, updated, deleted, stb. eseményekre lehet egyszerűen készíteni egy Model Observer-t, amely a modell esemény aktiválásakor lefuttat egy cache törlő metódust.



namespace App\Observers;

use App\Models\News;
use Illuminate\Support\Facades\Cache;

class NewsObserver
{
    /**
     * Listen to the User created event.
     *
     * @param  User  $user
     * @return void
     */
    public function saved()
    {
        $this->deleteAllNewsCache();
    }

    /**
     * Listen to the User deleting event.
     *
     * @param  User  $user
     * @return void
     */
    public function deleted()
    {
        $this->deleteAllNewsCache();
    }

    public function deleteAllNewsCache()
    {
        Cache::forget('news-home');

        $newsCount = News::where('status', 1)->where('enabled', 1)->count();
        $pages = floor($newsCount / ITEMS_PER_PAGE) + 1;

        for ($i=1; $i <= $pages; $i++) { 
            Cache::forget(md5('news'.$i.'.'.ITEMS_PER_PAGE));
        }
    }
}

De olyan esemény értelemszerűen nincs, hogy a státusz azaz egy flag az adatbázis táblában át lett állítva 1-ről 0-ra.

A Dokumentációt átbújva a következő megoldást találtam a problémára:

Az Event-eknél vannak az úgynevezett Event Subscriber osztályok (https://laravel.com/docs/5.3/events#event-subscribers). Ezek szerepe, hogy egy modellhez tartozó több eseményt is egy helyen tudjunk lekezelni. Ezt programkód rendezettség miatt érdemes használni, ha igényesek vagyuk a kódunkra [...].

1. Létrehoztam egy eseményt a parancssorban:

$ php artisan make:event ModelStatusChanged

Ezzel más dolgom nem volt, ez az esemény érthető leírása, “model státusza megváltozott”.

2. Következő lépésként nem egy külön Subscriber osztályt hoztam létre, hanem a dokumentációban is írt subscribe metódust helyeztem az Observer osztályba, a következő tartalommal:



namespace App\Observers;

use App\Models\News;
use Illuminate\Support\Facades\Cache;

class NewsObserver
{
    /**
     * Listen to the User created event.
     *
     * @param  User  $user
     * @return void
     */
    public function saved()
    {
        $this->deleteAllNewsCache();
    }

    /**
     * Listen to the User deleting event.
     *
     * @param  User  $user
     * @return void
     */
    public function deleted()
    {
        $this->deleteAllNewsCache();
    }

    public function subscribe($events)
    {
        $events->listen(
            'App\Events\ModelStatusChanged',
            'App\Observers\NewsObserver@deleteAllNewsCache'
        );
    }

    public function deleteAllNewsCache()
    {
        Cache::forget('news-home');

        $newsCount = News::where('status', 1)->where('enabled', 1)->count();
        $pages = floor($newsCount / ITEMS_PER_PAGE) + 1;

        for ($i=1; $i <= $pages; $i++) { 
            Cache::forget(md5('news'.$i.'.'.ITEMS_PER_PAGE));
        }
    }
}

Tehát az $event->listen első paramétere az esemény, amelyet majd meghívunk, a második pedig az az osztály/metódus, amelyet meg fog hívni a rendszer, ha az esemény létrejött.

3. Meg kell nyitni az EventServiceProvider fájlt, és elhelyezni benne egy property-t, amely jelzi a rendszernek, hogy van egy publish-subscribe patternű kód elhelyezve.



namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'App\Events\SomeEvent' => [
            'App\Listeners\EventListener',
        ],
    ];

    protected $subscribe = [
        'App\Observers\NewsObserver',
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();

        //
    }
}

Itt nem egy EventSubscriber-t adunk meg, hanem az Observer osztályunkat, az egyszerűség kedvéért. Tudni fogja a rendszer, hogy ebben az osztályben a subscribe metódust kell keresse.

4. Végül, meg kell hívjuk az eseményt, amikor a státusz módosításra kerül a modellben:



namespace App\Models;

use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;

class News extends Model
{
...
    public function setStatus($hash)
    {
        $news = static::where('hash', $hash)->firstOrFail();

        $news->status = ! $news->status;

        $news->save();

        event(new \App\Events\ModelStatusChanged($news));

    }
...
}

A probléma megoldva, jóllehet nem igazi modell eseményként lett regisztrálva a cikk státusz állítása, de a cache törlését ugyan úgy esemény figyeléssel és arra történő figyelő funkció futtatásával értük el.

Megjegyzés: kérdés lehet, hogy a subscribe metódus scope-ja miért nem private, mivel egy belső metódus, amelyet több más metódus hív meg az osztályon belül. A magyarázat az, hogy a listener rész, ('App\Observers\NewsObserver@deleteAllNewsCache') csak publikus metódust tud elérni és futtatni.

Cimkék:    subscriber    event
 Vissza a cikkekhez