Schreiben Sie weniger doppelten Code mit Bindemitteln in Laravel

Bild

Gute Zeit, meine Damen und Herren.

Vor nicht allzu langer Zeit bin ich bei der Überprüfung eines einzelnen Projekts in Laravel auf das Phänomen von doppeltem und doppeltem Code gestoßen.

Das Fazit lautet: Das System verfügt über eine interne API-Struktur für AJAX-Anforderungen, die im Wesentlichen eine Sammlung von Daten aus der Datenbank zurückgibt (Bestellungen, Benutzer, Kontingente usw.). Der springende Punkt dieser Struktur ist, JSON mit den Ergebnissen nicht mehr zurückzugeben. Bei der Codeüberprüfung habe ich 5 oder 6 Klassen mit demselben Code gezählt. Der einzige Unterschied bestand in der Abhängigkeitsinjektion von ResourceCollection, JsonResource und dem Modell selbst. Dieser Ansatz schien mir grundlegend falsch zu sein, und ich beschloss, die richtigen Änderungen an diesem Code mithilfe des leistungsstarken DI, das uns das Laravel Framework zur Verfügung stellt, selbst vorzunehmen.

Wie bin ich zu dem gekommen, worüber ich später sprechen werde?

Ich habe bereits ungefähr anderthalb Jahre Entwicklungserfahrung für Magento 2 und als ich zum ersten Mal auf dieses CMS stieß, war ich schockiert über dessen DI. Für diejenigen, die es nicht wissen: In Magento 2 basiert kein kleiner Teil des Systems auf den sogenannten "virtuellen Typen". Das heißt, wenn wir uns auf eine bestimmte Klasse beziehen, wenden wir uns nicht immer der "echten" Klasse zu. Wir beziehen uns auf einen virtuellen Typ, der basierend auf einer bestimmten "realen" Klasse "zusammengestellt" wurde (z. B. Sammlung für das Admin-Grid, zusammengestellt über DI). Das heißt, wir können tatsächlich jede Klasse zur Verwendung mit unseren Abhängigkeiten erstellen, indem wir einfach etwas Ähnliches in DI schreiben:

<virtualType name="Vendor\Module\Model\ResourceModel\MyData\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult"> <arguments> <argument name="mainTable" xsi:type="string">vendor_table</argument> <argument name="resourceModel" xsi:type="string">Vendor\Module\Model\ResourceModel\MyData </argument> </arguments> </virtualType> 

Wenn Sie jetzt die Klasse Vendor \ Module \ Model \ ResourceModel \ MyData \ Grid \ Collection anfordern, erhalten Sie eine Instanz von Magento \ Framework \ View \ Element \ UiComponent \ DataProvider \ SearchResult, wobei die Abhängigkeiten von mainTable auf "vendor_table" und resourceModel - "festgelegt sind. Vendor \ Module \ Model \ ResourceModel \ MyData. "

Anfangs schien mir ein solcher Ansatz nicht ganz klar, nicht ganz „angemessen“ und nicht ganz normal zu sein, aber nach einem Jahr der Entwicklung für diesen Ball wurde ich im Gegenteil ein Anhänger dieses Ansatzes, und außerdem fand ich in meinen Projekten Anwendung dafür .

Zurück nach Laravel.

DI Laravel basiert auf einem „Service-Container“ - einer Entität, die Bindemittel und Abhängigkeiten im System verwaltet. So können wir beispielsweise der DummyDataProviderInterface-Schnittstelle die Implementierung dieser DummyDataProvider-Schnittstelle selbst anzeigen.

 app()->bind(DummyDataProviderInterface::class, DummyDataProvider::class); 

Wenn wir dann DummyDataProviderInterface im Service-Container anfordern (z. B. über den Klassenkonstruktor), erhalten wir eine Instanz der DummyDataProvider-Klasse.

Viele (aus irgendeinem Grund) beenden dieses Wissen im Laravel-Servicecontainer und machen ihre eigenen, viel interessanteren Dinge, aber vergebens .

Laravel kann nicht nur reale Entitäten wie eine bestimmte Schnittstelle „binden“, sondern auch sogenannte „virtuelle Typen“ (auch bekannt als Aliase) erstellen. Und selbst in diesem Fall muss Laravel keine Klasse übergeben, die Ihren Typ implementiert. Die bind () -Methode kann eine anonyme Funktion als zweites Argument verwenden, wobei der Parameter $ app dort übergeben wird - eine Instanz der Anwendungsklasse. Im Allgemeinen befassen wir uns jetzt mehr mit der Kontextbindung, wobei das, was wir an die Klasse übergeben, die den "virtuellen Typ" implementiert, von der aktuellen Situation abhängt.

Ich warne Sie, dass nicht jeder mit diesem Ansatz zum Erstellen von Anwendungsarchitekturen einverstanden ist. Wenn Sie also ein Fan von Hunderten derselben Klassen sind, überspringen Sie dieses Material.

Zunächst werden wir entscheiden, was als "echte" Klasse fungieren soll. Nehmen wir am Beispiel eines Projekts, das mir bei einer Codeüberprüfung aufgefallen ist, die gleiche Situation mit Ressourcenanforderungen (tatsächlich CRUD, aber etwas reduziert).

Schauen wir uns die Implementierung eines gemeinsamen Crud-Controllers an:

 <?php namespace Wolf\Http\Controllers\Backend\Crud; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use Wolf\Http\Controllers\Controller; class BaseController extends Controller { /** * @var Model */ protected $model; /** * @var \Illuminate\Http\Resources\Json\ResourceCollection|null */ protected $resourceCollection; /** * @var \Illuminate\Http\Resources\Json\JsonResource|null */ protected $jsonResource; /** * BaseController constructor. * @param Model $model * @param \Illuminate\Http\Resources\Json\ResourceCollection|null $resourceCollection * @param \Illuminate\Http\Resources\Json\JsonResource|null $jsonResource */ public function __construct( $model, $resourceCollection = null, $jsonResource = null ) { $this->model = $model; $this->resourceCollection = $resourceCollection; $this->jsonResource = $jsonResource; } /** * Display a listing of the resource. * * @param Request $request * @return \Illuminate\Http\Resources\Json\ResourceCollection */ public function index(Request $request) { return $this->resourceCollection::make($this->model->get()); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Resources\Json\JsonResource */ public function show($id) { return $this->jsonResource::make($this->model->find($id)); } } 

Ich habe mich nicht viel um die Implementierung gekümmert, da sich das Projekt tatsächlich in der Planungsphase befindet.

Wir haben zwei Methoden, die uns etwas zurückgeben sollten: index, der eine Sammlung von Entitäten aus der Datenbank zurückgibt, und show, die die json-Ressource einer bestimmten Entität zurückgibt.

Wenn wir echte Klassen verwenden, erstellen wir jedes Mal eine Klasse mit 1-2 Setzern, die Klassen für Modelle, Ressourcen und Sammlungen definieren. Stellen Sie sich Dutzende von Dateien vor, von denen die wirklich komplexe Implementierung nur 1-2 ist. Mit DI Laravel können wir solche „Klone“ vermeiden.

Die Architektur dieses Systems wird also einfach, aber als Schweizer Uhr zuverlässig sein.
Es gibt eine JSON-Datei, die ein Array von "virtuellen Typen" enthält, die direkt auf die Klassen verweisen, die als Sammlungen, Modelle, Ressourcen usw. verwendet werden.

Zum Beispiel:

 { "Wolf\\Http\\Controllers\\Backend\\Crud\\OrdersResourceController": { "model": "Wolf\\Model\\Backend\\Order", "resourceCollection": "Wolf\\Http\\Resources\\OrdersCollection", "jsonResource": "Wolf\\Http\\Resources\\OrderResource" } } 

Als Nächstes setzen wir mithilfe der Laravel-Bindung Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController als unsere Klasse für unseren virtuellen Typ Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController als implementierende Klasse (beachten Sie, dass die Klasse sollte nicht abstrakt sein, da beim Anfordern von Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController eine Instanz von Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController (keine abstrakte Klasse) abgerufen werden sollte.

Geben Sie in CrudServiceProvider in der boot () -Methode den folgenden Code ein:

 $path = app_path('etc/crud.json'); if ($this->filesystem->isFile($path)) { $virtualTypes = json_decode($this->filesystem->get($path), true); foreach ($virtualTypes as $virtualType => $data) { $this->app->bind($virtualType, function ($app) use ($data) { /** @var Application $app */ $bindingData = [ 'model' => $app->make($data['model']), 'resourceCollection' => $data['resourceCollection'], 'jsonResource' => $data['jsonResource'] ]; return $app->makeWith(self::BASE_CRUD_CONTROLLER, $bindingData); }); } } 

Die Konstante BASE_CRUD_CONTROLLER enthält den Namen der Klasse, die die Logik des CRUD-Controllers implementiert.

Weit davon entfernt, ideal zu sein, aber es funktioniert :)

Hier gehen wir durch ein Array mit virtuellen Typen und setzen die Bindemittel. Beachten Sie, dass wir die Modellinstanz nur aus dem Service-Container abrufen und ResourceCollection und JsonResource nur Klassennamen sind. Warum so? Das Modell muss die zu füllenden Attribute nicht aufnehmen, es kann auch ohne sie auskommen. Sammlungen müssen jedoch eine Art Ressource aufnehmen, aus der sie Daten und Entitäten erhalten. Daher verwenden wir in BaseController die statischen Methoden collection () bzw. make () (im Prinzip können wir dynamische Getter hinzufügen, die etwas in die Ressource einfügen und eine Instanz an uns zurückgeben, aber ich überlasse dies Ihnen), die uns Instanzen davon zurückgeben gleiche Sammlungen, aber mit den Daten auf sie übertragen.

Tatsächlich können Sie im Prinzip die gesamte Laravel-Bindung in einen solchen Zustand bringen.

Wenn wir Wolf \ Http \ Controller \ Backend \ Crud \ OrdersResourceController anfordern, erhalten wir insgesamt eine Instanz des Controllers Wolf \ Http \ Controller \ Backend \ Crud \ BaseController, jedoch mit integrierten Abhängigkeiten unseres Modells, unserer Ressource und unserer Sammlung. Es müssen nur noch eine ResourceCollection und JsonResource erstellt werden, und Sie können die zurückgegebenen Daten steuern.

Source: https://habr.com/ru/post/de451462/


All Articles