首页 > 后端开发 > 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
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板