Waktu yang baik, tuan dan nyonya.
Belum lama ini saya menemukan fenomena duplikat dan kode duplikat ketika meninjau satu proyek di Laravel.
Intinya adalah: sistem memiliki beberapa struktur API internal untuk permintaan AJAX, pada dasarnya mengembalikan koleksi sesuatu dari database (pesanan, pengguna, kuota, dll ...). Inti dari struktur ini adalah mengembalikan JSON dengan hasilnya, tidak lebih. Dalam ulasan kode, saya menghitung 5 atau 6 kelas menggunakan kode yang sama, satu-satunya perbedaan adalah pada injeksi dependensi dari ResourceCollection, JsonResource, dan model itu sendiri. Pendekatan ini pada dasarnya keliru bagi saya, dan saya memutuskan untuk membuat saya sendiri, seperti yang saya yakini, mengubah kode ini dengan benar, menggunakan DI yang kuat yang disediakan oleh Laravel Framework.
Jadi, bagaimana saya sampai pada apa yang akan saya bicarakan nanti.
Saya sudah memiliki sekitar setengah tahun pengalaman pengembangan untuk Magento 2, dan ketika saya pertama kali menemukan CMS ini, saya terkejut dengan DI-nya. Bagi mereka yang tidak tahu: di Magento 2, tidak ada bagian kecil dari sistem yang dibangun di atas apa yang disebut "tipe virtual". Artinya, merujuk pada kelas tertentu, kita tidak selalu beralih ke kelas "nyata". Kami merujuk pada tipe virtual, yang "dirakit" berdasarkan kelas "nyata" tertentu (misalnya, Koleksi untuk kisi admin, dikumpulkan melalui DI). Artinya, kita benar-benar dapat membangun kelas apa saja untuk digunakan dengan dependensi kita dengan hanya menulis sesuatu yang serupa di 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>
Sekarang, dengan meminta kelas Vendor \ Module \ Model \ ResourceModel \ MyData \ Grid \ Collection, kita mendapatkan instance dari Magento \ Framework \ View \ Element \ UiComponent \ DataProvider \ SearchResult, tetapi dengan dependensi mainTable diatur ke "vendor_table" dan resourceModel - " Vendor \ Module \ Model \ ResourceModel \ MyData. "
Pada awalnya, pendekatan seperti itu tampaknya tidak sepenuhnya jelas bagi saya, tidak sepenuhnya "sesuai" dan tidak cukup normal, tetapi setelah satu tahun pengembangan
untuk bola ini , saya, sebaliknya, menjadi pengikut pendekatan ini, dan, lebih lagi, saya menemukan aplikasi untuk itu dalam proyek-proyek saya .
Kembali ke Laravel.
DI Laravel dibangun di atas "wadah layanan" - entitas yang mengelola binder dan dependensi dalam sistem. Jadi, kita dapat, misalnya, menunjukkan ke antarmuka DummyDataProviderInterface implementasi antarmuka DummyDataProvider ini sendiri.
app()->bind(DummyDataProviderInterface::class, DummyDataProvider::class);
Kemudian, ketika kami meminta DummyDataProviderInterface di wadah layanan (misalnya, melalui konstruktor kelas), kami mendapatkan turunan dari kelas DummyDataProvider.
Banyak
(untuk beberapa alasan) mengakhiri pengetahuan ini dalam wadah layanan Laravel dan pergi untuk melakukan hal-hal mereka sendiri yang jauh lebih menarik,
tetapi sia-sia .
Laravel dapat "mengikat" tidak hanya entitas nyata, seperti antarmuka yang diberikan, tetapi juga membuat apa yang disebut "tipe virtual" (alias alias). Dan, bahkan dalam kasus ini, Laravel tidak harus lulus kelas yang mengimplementasikan tipe Anda. Metode bind () dapat menggunakan fungsi anonim sebagai argumen kedua, dengan parameter $ app diteruskan ke sana - turunan dari kelas aplikasi. Secara umum, sekarang kita lebih ke pengikatan kontekstual, di mana apa yang kita berikan ke kelas yang mengimplementasikan "tipe virtual" tergantung pada situasi saat ini.
Saya memperingatkan Anda bahwa tidak semua orang setuju dengan pendekatan ini untuk membangun arsitektur aplikasi, jadi jika Anda adalah penggemar ratusan kelas yang sama, lewati materi ini.
Jadi, sebagai permulaan kita akan memutuskan apa yang akan bertindak sebagai kelas "nyata". Menggunakan contoh proyek yang datang kepada saya dalam tinjauan kode, mari kita ambil situasi yang sama dengan permintaan sumber daya (pada kenyataannya, CRUD, tetapi sedikit dikurangi).
Mari kita lihat implementasi dari kontroler Crud yang umum:
<?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)); } }
Saya tidak terlalu peduli dengan implementasinya, karena proyek ini pada tahap perencanaan, sebenarnya.
Kami memiliki dua metode yang harus mengembalikan sesuatu kepada kami: indeks, yang mengembalikan koleksi entitas dari database, dan menunjukkan, yang mengembalikan sumber daya json dari entitas tertentu.
Jika kami menggunakan kelas nyata, setiap kali kami akan membuat kelas yang berisi 1-2 setter yang akan mendefinisikan kelas untuk model, sumber daya dan koleksi. Bayangkan lusinan file, yang implementasi yang benar-benar kompleks hanya 1-2. Kita dapat menghindari "klon" seperti itu menggunakan DI Laravel.
Jadi, arsitektur sistem ini akan sederhana, tetapi dapat diandalkan sebagai jam tangan Swiss.
Ada file json yang berisi larik "tipe virtual" dengan referensi langsung ke kelas yang akan digunakan sebagai koleksi, model, sumber daya, dll ...
Sebagai contoh, ini:
{ "Wolf\\Http\\Controllers\\Backend\\Crud\\OrdersResourceController": { "model": "Wolf\\Model\\Backend\\Order", "resourceCollection": "Wolf\\Http\\Resources\\OrdersCollection", "jsonResource": "Wolf\\Http\\Resources\\OrderResource" } }
Selanjutnya, menggunakan Laravel binding, kita akan menetapkan Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController sebagai kelas kami untuk tipe virtual kami Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController sebagai kelas pelaksana (perhatikan bahwa kelas tersebut tidak boleh abstrak, karena ketika meminta Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController kita harus mendapatkan instance dari Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController, bukan kelas abstrak).
Di CrudServiceProvider, dalam metode boot (), masukkan kode berikut:
$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); }); } }
BASE_CRUD_CONTROLLER konstan berisi nama kelas yang mengimplementasikan logika pengontrol CRUD.
Jauh dari ideal, tetapi berhasil :)
Di sini kita pergi melalui array dengan tipe virtual dan mengatur binder. Perhatikan bahwa kita hanya mendapatkan contoh model dari wadah layanan, dan ResourceCollection dan JsonResource hanya nama kelas. Kenapa begitu Model tidak harus mengambil atribut untuk diisi, itu bisa dilakukan tanpa mereka. Tetapi koleksi harus mengambil beberapa jenis sumber daya dari mana mereka akan mendapatkan data dan entitas. Oleh karena itu, di BaseController kita menggunakan pengumpulan metode statis () dan membuat () masing-masing (pada prinsipnya, kita dapat menambahkan getter dinamis yang akan memasukkan sesuatu ke sumber daya dan mengembalikan sebuah instance kepada kami, tapi saya akan menyerahkan ini kepada Anda) yang akan mengembalikan kepada kami contoh ini koleksi yang sama, tetapi dengan data yang ditransfer ke mereka.
Bahkan, pada prinsipnya, Anda dapat membawa seluruh Laravel mengikat ke keadaan seperti itu.
Secara total, meminta Wolf \ Http \ Controllers \ Backend \ Crud \ OrdersResourceController kita mendapatkan instance dari controller Wolf \ Http \ Controllers \ Backend \ Crud \ BaseController, tetapi dengan dependensi built-in dari model, sumber daya, dan koleksi kami. Tetap hanya untuk membuat ResourceCollection dan JsonResource dan Anda dapat mengontrol data yang dikembalikan.