Sebagai pengaturcara, saya telah menemui banyak projek selama bertahun-tahun, yang saya warisi, urus, naik taraf, bangunkan dan serahkan. Ramai daripada mereka melibatkan kod spageti atau, sebagaimana ia juga dipanggil, "bola besar lumpur." Isu ini selalunya mempengaruhi projek yang dibina pada beberapa rangka kerja, dengan kod disusun sama seperti contoh dalam dokumentasi rangka kerja.
Malangnya, dokumentasi rangka kerja MVC sering gagal memberi amaran bahawa contoh kod terutamanya bertujuan untuk menggambarkan kefungsian dan tidak sesuai untuk aplikasi dunia sebenar. Akibatnya, projek sebenar sering mengintegrasikan semua lapisan ke dalam kaedah pengawal atau penyampai (dalam kes MVP), yang memproses permintaan (biasanya permintaan HTTP). Jika rangka kerja termasuk Model Objek Komponen, seperti Nette, komponen selalunya merupakan sebahagian daripada pengawal atau penyampai, merumitkan lagi keadaan.
Kod projek sedemikian cepat berkembang panjang dan kerumitan. Dalam skrip tunggal, operasi pangkalan data, manipulasi data, permulaan komponen, tetapan templat dan logik perniagaan bercampur. Walaupun pengarang kadang-kadang mengekstrak bahagian fungsi ke dalam perkhidmatan kendiri (biasanya orang tunggal), ini jarang membantu. Projek sebegini menjadi sukar dibaca dan sukar diselenggara.
Dari pengalaman saya, corak reka bentuk piawai jarang digunakan, terutamanya dalam projek yang lebih kecil (5–50 ribu baris kod), seperti aplikasi CRUD mudah untuk perniagaan kecil yang ingin memudahkan pentadbiran. Namun, projek ini boleh mendapat banyak manfaat daripada corak seperti CQRS (Pengasingan Tanggungjawab Pertanyaan Perintah) dan DDD (Reka Bentuk Dipacu Domain).
Saya akan menunjukkan bagaimana pendekatan ini kelihatan menggunakan susunan Nette, Contributte (dengan penyepaduan Symfony Event Dispatcher) dan Nextras ORM.
// Command definition final class ItemSaveCommand implements Command { private ItemSaveRequest $request; public function __construct(private Orm $orm) { // } /** @param ItemSaveRequest $request */ public function setRequest(Request $request): void { $this->request = $request; } public function execute(): void { $request = $this->request; if ($request->id) { $entity = $this->orm->items->getById($request->id); } else { $entity = new Item(); $entity->uuid = $request->uuid; } $entity->data = $request->data; $this->orm->persist($entity); } } // Command Factory interface ItemSaveCommandFactory extends FactoryService { public function create(): ItemSaveCommand; } // Request #[RequestCommandFactory(ItemSaveCommandFactory::class)] final class ItemSaveRequest implements Request { public int|null $id = null; public string $uuid; public string $data; } /** * Command execution service * Supports transactions and request logging */ final class CommandExecutionService implements Service { private \DateTimeImmutable $requestedAt; public function __construct( private Orm $orm, private Container $container, private Connection $connection, ) { $this->requestedAt = new \DateTimeImmutable(); } /** @throws \Throwable */ public function execute(AbstractCommandRequest $request, bool $logRequest = true, bool $transaction = true): mixed { $factoryClass = RequestHelper::getCommandFactory($request); $factory = $this->container->getByType($factoryClass); $cmd = $factory->create(); $clonedRequest = clone $request; $cmd->setRequest($request); try { $cmd->execute(); if (!$transaction) { $this->orm->flush(); } if (!$logRequest) { return; } $logEntity = RequestHelper::createRequestLog( $clonedRequest, $this->requestedAt, RequestLogConstants::StateSuccess ); if ($transaction) { $this->orm->persistAndFlush($logEntity); } else { $this->orm->persist($logEntity); } return; } catch (\Throwable $e) { if ($transaction) { $this->connection->rollbackTransaction(); } if (!$logRequest) { throw $e; } $logEntity = RequestHelper::createRequestLog( $clonedRequest, $this->requestedAt, RequestLogConstants::StateFailed ); if ($transaction) { $this->orm->persistAndFlush($logEntity); } else { $this->orm->persist($logEntity); } throw $e; } } } // Listener for executing commands via Event Dispatcher final class RequestExecutionListener implements EventSubscriberInterface { public function __construct( private CommandExecutionService $commandExecutionService ) { // } public static function getSubscribedEvents(): array { return [ RequestExecuteEvent::class => 'onRequest' ]; } /** @param ExecuteRequestEvent<mixed> $ev */ public function onRequest(ExecuteRequestEvent $ev): void { $this->commandExecutionService->execute($ev->request, $ev->logRequest, $ev->transaction); } } // Event definition for command execution final class ExecuteRequestEvent extends Event { public function __construct( public Request $request, public bool $logRequest = true, public bool $transaction = true, ) { // Constructor } } // Event Dispatcher Facade final class EventDispatcherFacade { public static EventDispatcherInterface $dispatcher; public static function set(EventDispatcherInterface $dispatcher): void { self::$dispatcher = $dispatcher; } } // Helper function for simple event dispatching function dispatch(Event $event): object { return EventDispatcherFacade::$dispatcher->dispatch($event); } // Usage in Presenter (e.g., in response to a component event) final class ItemPresenter extends Presenter { public function createComponentItem(): Component { $component = new Component(); $component->onSave[] = function (ItemSaveRequest $request) { dispatch(new ExecuteRequestEvent($request)); }; return $component; } }
Penyelesaian ini mempunyai beberapa kelebihan. Logik berkaitan pangkalan data diasingkan daripada MVC/P, menyumbang kepada kebolehbacaan yang lebih baik dan penyelenggaraan yang lebih mudah. Objek permintaan, yang bertindak sebagai pembawa data, sesuai untuk log masuk ke pangkalan data, seperti log peristiwa. Ini memastikan bahawa semua input pengguna yang mengubah suai data disimpan bersama dengan susunan kronologinya. Sekiranya berlaku ralat, log ini boleh disemak dan dimainkan semula jika perlu.
Kelemahan pendekatan ini termasuk hakikat bahawa arahan tidak sepatutnya mengembalikan sebarang data. Oleh itu, jika saya perlu bekerja lebih jauh dengan data yang baru dibuat (cth., hantar ke templat), saya mesti mendapatkannya menggunakan UUIDnya, itulah sebabnya kedua-dua permintaan dan entiti mengandunginya. Kelemahan lain ialah sebarang perubahan pada skema pangkalan data memerlukan pengemaskinian semua permintaan agar sepadan dengan skema baharu, yang boleh memakan masa.
Atas ialah kandungan terperinci Kod yang lebih maju sedikit daripada contoh dalam dokumentasi rangka kerja.. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!