Bon moment, mesdames et messieurs.
Il n'y a pas si longtemps, j'ai rencontré le phénomène de doublon et de code en double lors de l'examen d'un seul projet dans Laravel.
L'essentiel est: le système a une certaine structure de l'API interne pour les demandes AJAX, renvoyant essentiellement une collection de quelque chose de la base de données (commandes, utilisateurs, quotas, etc ...). L'intérêt de cette structure est de renvoyer JSON avec les résultats, pas plus. Dans la revue de code, j'ai compté 5 ou 6 classes utilisant le même code, la seule différence résidait dans l'injection de dépendances de ResourceCollection, JsonResource et le modèle lui-même. Cette approche me semblait fondamentalement erronée, et j'ai décidé d'apporter mes propres modifications, comme je le crois, à ce code, en utilisant la puissante DI que Laravel Framework nous fournit.
Alors, comment en suis-je arrivé à ce dont je parlerai plus tard.
J'ai déjà environ un an et demi d'expérience de développement pour Magento 2, et quand j'ai rencontré ce CMS pour la première fois, j'ai été choqué par sa DI. Pour ceux qui ne connaissent pas: dans Magento 2, pas une petite partie du système n'est construite sur les soi-disant "types virtuels". C'est-à-dire que, se référant à une classe particulière, nous ne nous tournons pas toujours vers la "vraie" classe. Nous nous référons au type virtuel, qui a été "assemblé" sur la base d'une certaine classe "réelle" (par exemple, Collection pour la grille d'administration, collectée via DI). Autrement dit, nous pouvons réellement créer n'importe quelle classe à utiliser avec nos dépendances en écrivant simplement quelque chose de similaire dans DI:
<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>
Maintenant, en demandant la classe Vendor \ Module \ Model \ ResourceModel \ MyData \ Grid \ Collection, nous obtenons une instance de Magento \ Framework \ View \ Element \ UiComponent \ DataProvider \ SearchResult, mais avec les dépendances mainTable définies sur "vendor_table" et resourceModel - " Vendor \ Module \ Model \ ResourceModel \ MyData. "
Au début, une telle approche ne me paraissait pas tout à fait claire, pas tout à fait «appropriée» et pas tout à fait normale, mais après un an de développement
pour ce ballon , je suis au contraire devenu un adepte de cette approche, et d'ailleurs j'ai trouvé une application pour cela dans mes projets .
Retour à Laravel.
DI Laravel est construit sur un «conteneur de services» - une entité qui gère les liants et les dépendances dans le système. Ainsi, nous pouvons, par exemple, indiquer à l'interface DummyDataProviderInterface l'implémentation de cette interface DummyDataProvider elle-même.
app()->bind(DummyDataProviderInterface::class, DummyDataProvider::class);
Ensuite, lorsque nous demandons DummyDataProviderInterface dans le conteneur de service (par exemple, via le constructeur de classe), nous obtenons une instance de la classe DummyDataProvider.
Beaucoup
(pour une raison quelconque) mettent fin à ces connaissances dans le conteneur de service Laravel et vont faire leurs propres choses, beaucoup plus intéressantes,
mais en vain .
Laravel peut «lier» non seulement des entités réelles, telles qu'une interface donnée, mais également créer des «types virtuels» (alias). Et, même dans ce cas, Laravel n'a pas à passer une classe qui implémente votre type. La méthode bind () peut prendre une fonction anonyme comme deuxième argument, avec le paramètre $ app passé - une instance de la classe d'application. En général, nous allons maintenant plus dans la liaison contextuelle, où ce que nous passons à la classe qui implémente le "type virtuel" dépend de la situation actuelle.
Je vous avertis que tout le monde n'est pas d'accord avec cette approche de la construction d'une architecture d'application, donc si vous êtes fan de centaines de mêmes classes, sautez ce matériau.
Donc, pour commencer, nous déciderons ce qui agira comme une «vraie» classe. En utilisant l'exemple d'un projet qui m'est venu lors d'une révision de code, prenons la même situation avec les demandes de ressources (en fait, CRUD, mais un peu réduites).
Examinons la mise en œuvre d'un contrôleur Crud commun:
<?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 { protected $model; protected $resourceCollection; protected $jsonResource; public function __construct( $model, $resourceCollection = null, $jsonResource = null ) { $this->model = $model; $this->resourceCollection = $resourceCollection; $this->jsonResource = $jsonResource; } public function index(Request $request) { return $this->resourceCollection::make($this->model->get()); } public function show($id) { return $this->jsonResource::make($this->model->find($id)); } }
Je ne me suis pas beaucoup soucié de la mise en œuvre, car le projet est en fait au stade de la planification.
Nous avons deux méthodes qui devraient nous renvoyer quelque chose: index, qui retourne une collection d'entités de la base de données, et show, qui retourne la ressource json d'une entité spécifique.
Si nous utilisions de vraies classes, nous créerions à chaque fois une classe contenant 1 à 2 setters qui définiraient des classes pour les modèles, les ressources et les collections. Imaginez des dizaines de fichiers, dont l'implémentation vraiment complexe n'est que 1-2. Nous pouvons éviter de tels «clones» en utilisant DI Laravel.
Ainsi, l'architecture de ce système sera simple, mais fiable comme une montre suisse.
Il existe un fichier json qui contient un tableau de "types virtuels" avec une référence directe aux classes qui seront utilisées comme collections, modèles, ressources, etc ...
Par exemple, ceci:
{ "Wolf\\Http\\Controllers\\Backend\\Crud\\OrdersResourceController": { "model": "Wolf\\Model\\Backend\\Order", "resourceCollection": "Wolf\\Http\\Resources\\OrdersCollection", "jsonResource": "Wolf\\Http\\Resources\\OrderResource" } }
Ensuite, en utilisant la liaison Laravel, nous allons définir Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController comme notre classe pour notre type virtuel Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController comme classe d'implémentation (notez que la classe ne doit pas être abstrait, car lorsque vous demandez Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController, nous devons obtenir une instance de Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController, pas une classe abstraite).
Dans CrudServiceProvider, dans la méthode boot (), mettez le code suivant:
$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) { $bindingData = [ 'model' => $app->make($data['model']), 'resourceCollection' => $data['resourceCollection'], 'jsonResource' => $data['jsonResource'] ]; return $app->makeWith(self::BASE_CRUD_CONTROLLER, $bindingData); }); } }
La constante BASE_CRUD_CONTROLLER contient le nom de la classe qui implémente la logique du contrôleur CRUD.
Loin d'être idéal, mais ça marche :)
Ici, nous passons par un tableau avec des types virtuels et définissons les liants. Notez que nous obtenons uniquement l'instance de modèle à partir du conteneur de services, et ResourceCollection et JsonResource ne sont que des noms de classe. Pourquoi Le modèle n'a pas à prendre en compte les attributs à remplir, il peut bien s'en passer. Mais les collections doivent absorber une sorte de ressource à partir de laquelle elles obtiendront des données et des entités. Par conséquent, dans BaseController, nous utilisons respectivement les méthodes statiques collection () et make () (en principe, nous pouvons ajouter des getters dynamiques qui mettront quelque chose dans la ressource et nous renverront une instance, mais je vous le laisserai) qui nous renverra des instances de ces mêmes collections, mais avec les données qui leur sont transférées.
En fait, vous pouvez, en principe, amener la liaison Laravel entière à un tel état.
Au total, en demandant Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController, nous obtenons une instance du contrôleur Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController, mais avec des dépendances intégrées de notre modèle, ressource et collection. Il ne reste plus qu'à créer un ResourceCollection et JsonResource et vous pouvez contrôler les données retournées.