ホームページ > バックエンド開発 > PHPチュートリアル > フレームワークのドキュメントの例よりもわずかに高度なコードです。

フレームワークのドキュメントの例よりもわずかに高度なコードです。

Patricia Arquette
リリース: 2024-12-27 19:06:12
オリジナル
387 人が閲覧しました

Slightly more advanced code than the example in the frameworks documentation.

私はプログラマーとして、長年にわたって数多くのプロジェクトに遭遇し、それらを継承、管理、アップグレード、開発、引き渡してきました。それらの多くは、スパゲッティコード、または「大きな泥の団子」とも呼ばれるものに関係していました。この問題は、コードがフレームワークのドキュメントの例と同様に編成されている、いくつかのフレームワーク上に構築されたプロジェクトに影響を与えることがよくあります。

残念ながら、MVC フレームワークのドキュメントでは、コード例は主に機能を説明することを目的としており、現実世界のアプリケーションには適していないことを警告していないことがよくあります。その結果、実際のプロジェクトでは、多くの場合、すべてのレイヤーがコントローラーまたはプレゼンター メソッド (MVP の場合) に統合され、リクエスト (通常は HTTP リクエスト) が処理されます。フレームワークに Nette などのコンポーネント オブジェクト モデルが含まれている場合、コンポーネントはコントローラーまたはプレゼンターの一部であることが多く、状況はさらに複雑になります。

最適でないコード構造の問題

このようなプロジェクトのコードは、すぐに長さと複雑さが増大します。単一のスクリプトには、データベース操作、データ操作、コンポーネントの初期化、テンプレート設定、ビジネス ロジックが混在しています。作成者は機能の一部をスタンドアロン サービス (通常はシングルトン) に抽出することがありますが、これがあまり役立つことはほとんどありません。このようなプロジェクトは読みにくく、保守も難しくなります。

私の経験から言えば、標準化された設計パターンは、特に管理の簡素化を目指す中小企業向けの単純な CRUD アプリケーションなどの小規模なプロジェクト (コード 5 ~ 50,000 行) ではほとんど使用されません。しかし、これらのプロジェクトは、CQRS (コマンド クエリ責任分離) や DDD (ドメイン駆動設計) などのパターンから大きな恩恵を受ける可能性があります。

  1. HTTP リクエスト ->コントローラー/プレゼンター
  2. 入力検証 ->リクエストに変換
  3. リクエスト ->コマンド
  4. コマンド ->レンダリング/応答

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 サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート