Csatlakozz a Slack chatre
Szalai Barna / 2 éve

Composite pattern, Laravel módra

A SOLID elveket nem kevesen ismerik már, arról is hallotak sokan, hogy a controller-ünk ne legyen telezsúfolva funkcionális programkódokkal. Ezt eleinte, amikor MVC rendszert keztünk használi szinte mindenki figyelmen kívül hagyta, vagy nem is hallott róla, és elérhettük, hogy óriási, átláthatatlan metódusokat gyártottunk, egybe volt minden végrehajtandó feladat, de azért kicsit éreztük, hogy ez nem nagyon van jól.

Volt egy olyan mondás is, hogy “fat models, skinny controllers” (azaz kövér modelek – ebben legyen a lényeg, vékony controllerek – itt pedig csak a model hívásai..), sokat olvastam IRC-n jópár évvel ezelőtt, de ez is idejemúlt lett az idők során, és már több féle megoldás létezik kódunk funkcióinak szeparálására, ezek közül az egyik a Composite pattern használata.

Tehát azt kell szemünk előtt tartani, hogy alapvetően a controller feladata request-ek fogadása, és válaszolás response-okkal. E kettő között pedig a feladatokat ki kell osszuk szervízeknek, queue job-oknak, command bus megoldásoknak. A következő megoldás, megtalálható a Laravel core működésében, és miért ne vegyünk ötleteket, hogy ott hogyan működnek egyes dolgok.

Először is, vegyünk egy controller-t amely kap egy request-et, és annak feldolgozását és egyéb műveletek elvégzését kiadja egy Task nevű szervíznek. (A kód egyes részei sematikusak, ez nem kezdő cikk, a nem releváns részek nincsenek részletezve)
 

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Services\Task;

class TaskController extends Controller
{
    public function index()
    {
        // lekérjük a requrest-ből az adatokat

        (new Task([
            'email' => 'test@test.hu',
            'data' => 'ez a data',
            'log' => 'naplozandó adatok'
        ]))->handle();

        // visszadunk egy view reponse-t
    }
}

A Task szervíznek, ahogy a Laravel rendszerben is sok helyen, van egy handle() metódusa, amely a lényegi részt végzi el. A construtor-on keresztül adjuk át az adatokat, amelyeket fel kell dolgozni. A példában teoretikusan el kell menteni a kapott adatokat adatbázisba, erről kell egy rendszer emailt küldeni, végül a tevékenységet naplózni kell. Ez három feladat, ezeket is szeparáljuk, és egy-egy osztályba helyezzük el. Tehát, a handle() metódusban felvisszük a task-okat, egy tömbbe, melyet egy foreach ciklussal örököltetünk, átadjuk a Task osztály $this változóját, ami az objektum összes property-jét tartalmazza, továbbá meghívjuk a sub-task osztály handle() metódusát is.
Ezáltal könnyen hivatkozhatunk a sub-taskokban, a szükséges változókra, és tudunk velük dolgozni.
 

namespace App\Services;

class Task {

    public $email;
    public $data;
    public $log;

    public function __construct(array $data)
    {
        $this->data = $data['data'];
        $this->email = $data['email'];
        $this->log = $data['log'];
    }

    public function handle()
    {
        $tasks = [
            TaskSave::class,
            TaskSendEmail::class,
            TaskLog::class
        ];

        foreach ($tasks as $task) {
            (new $task($this))->handle();
        }
    }
}

A szeparált task-ok átveszik a constructor-ukban a szülő objektumot, és a handle() metódusban máris tudunk rá/rájuk hivatkozni és feldolgozni őket.

 

namespace App\Services;

class TaskSave {

    protected $parent;

    public function __construct($parent)
    {
        $this->parent = $parent;
    }

    public function handle()
    {
        // elmentjük az adatokat DB-be
        echo $this->parent->data;
    }
}

---

namespace App\Services;

class TaskSendEmail {

    protected $parent;

    public function __construct($parent)
    {
        $this->parent = $parent;
    }

    public function handle()
    {
        // kiküldjük a rendszer emailt
        echo $this->parent->email;
    }
}

---

namespace App\Services;

class TaskLog {

    protected $parent;

    public function __construct($parent)
    {
        $this->parent = $parent;
    }

    public function handle()
    {
        // elvégettük a naplózást.	
        echo $this->parent->log;
    }
} 

Sokkal átláthatóbb, és jól menedzselhető kódot kapunk ezáltal, mintha mindent a controller metódusaiba szűkítenénk.  

 Vissza a cikkekhez