首頁 > 後端開發 > php教程 > 比框架文檔中的範例稍微高級一些的程式碼。

比框架文檔中的範例稍微高級一些的程式碼。

Patricia Arquette
發布: 2024-12-27 19:06:12
原創
387 人瀏覽過

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

作為一名程式設計師,這些年我遇到了無數的項目,我繼承、管理、升級、開發、移交。其中許多涉及義大利麵代碼,或也稱為“大泥球”。此問題通常會影響基於某些框架建構的項目,其中程式碼的組織方式與框架文件中的範例類似。

不幸的是,MVC 框架的文件通常無法警告程式碼範例主要用於說明功能,並不適合實際應用程式。因此,實際專案通常將所有層整合到處理請求(通常是 HTTP 請求)的控制器或呈現器方法(在 MVP 的情況下)。如果框架包含組件物件模型(例如 Nette),則組件通常是控制器或演示器的一部分,這會使情況更加複雜。

非最佳程式碼結構的問題

此類專案的程式碼長度和複雜性都會快速成長。在單一腳本中,資料庫操作、資料操作、元件初始化、範本設定和業務邏輯混合在一起。雖然作者偶爾會將部分功能提取到獨立服務(通常是單例)中,但這幾乎沒有太大幫助。這樣的項目變得難以閱讀且難以維護。

根據我的經驗,很少使用標準化設計模式,特別是在較小的專案(5-5萬行程式碼)中,例如尋求簡化管理的小型企業的簡單 CRUD 應用程式。然而,這些項目可以極大地受益於 CQRS(命令查詢職責分離)和 DDD(領域驅動設計)等模式。

  1. HTTP 請求 ->控制者/演示者
  2. 輸入驗證->轉換為請求
  3. 請求 ->指令
  4. 指令 ->渲染/回應

我將使用 Nette 堆疊、Contributte(與 Symfony 事件調度程式整合)和 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 檢索它,這就是為什麼請求和實體都包含它的原因。另一個缺點是對資料庫架構的任何變更都需要更新所有請求以匹配新架構,這可能非常耗時。

以上是比框架文檔中的範例稍微高級一些的程式碼。的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板