私はプログラマーとして、長年にわたって数多くのプロジェクトに遭遇し、それらを継承、管理、アップグレード、開発、引き渡してきました。それらの多くは、スパゲッティコード、または「大きな泥の団子」とも呼ばれるものに関係していました。この問題は、コードがフレームワークのドキュメントの例と同様に編成されている、いくつかのフレームワーク上に構築されたプロジェクトに影響を与えることがよくあります。
残念ながら、MVC フレームワークのドキュメントでは、コード例は主に機能を説明することを目的としており、現実世界のアプリケーションには適していないことを警告していないことがよくあります。その結果、実際のプロジェクトでは、多くの場合、すべてのレイヤーがコントローラーまたはプレゼンター メソッド (MVP の場合) に統合され、リクエスト (通常は HTTP リクエスト) が処理されます。フレームワークに Nette などのコンポーネント オブジェクト モデルが含まれている場合、コンポーネントはコントローラーまたはプレゼンターの一部であることが多く、状況はさらに複雑になります。
このようなプロジェクトのコードは、すぐに長さと複雑さが増大します。単一のスクリプトには、データベース操作、データ操作、コンポーネントの初期化、テンプレート設定、ビジネス ロジックが混在しています。作成者は機能の一部をスタンドアロン サービス (通常はシングルトン) に抽出することがありますが、これがあまり役立つことはほとんどありません。このようなプロジェクトは読みにくく、保守も難しくなります。
私の経験から言えば、標準化された設計パターンは、特に管理の簡素化を目指す中小企業向けの単純な CRUD アプリケーションなどの小規模なプロジェクト (コード 5 ~ 50,000 行) ではほとんど使用されません。しかし、これらのプロジェクトは、CQRS (コマンド クエリ責任分離) や DDD (ドメイン駆動設計) などのパターンから大きな恩恵を受ける可能性があります。
Nette スタック、Contributte (Symfony Event Dispatcher 統合)、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; } }
このソリューションにはいくつかの利点があります。データベース関連のロジックが MVC/P から分離されているため、可読性が向上し、メンテナンスが容易になります。データ キャリアとして機能するリクエスト オブジェクトは、イベント ログなどのデータベースへのログインに最適です。これにより、データを変更するすべてのユーザー入力が時系列で保存されるようになります。エラーが発生した場合、これらのログを確認し、必要に応じて再実行できます。
このアプローチの欠点としては、コマンドがデータを返さないことが挙げられます。したがって、新しく作成したデータをさらに操作する必要がある場合 (例: テンプレートに渡す場合)、その UUID を使用してデータを取得する必要があります。そのため、リクエストとエンティティの両方にデータが含まれています。もう 1 つの欠点は、データベース スキーマを変更すると、新しいスキーマに一致するようにすべてのリクエストを更新する必要があり、時間がかかる可能性があることです。
以上がフレームワークのドキュメントの例よりもわずかに高度なコードです。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。