iuravic/duktig-core

Duktig MVC 微型 Web 框架的 PHP 7.1 核心库

dev-master / 1.0.x-dev 2017-07-07 10:52 UTC

This package is not auto-updated.

Last update: 2024-09-29 01:49:10 UTC


README

Build Status Coverage Status

duktig-core

这是 Duktig 微型 MVC Web 框架的核心包。

使用 Duktig 框架的全功能原型 Web 项目包含在 duktig-skeleton-web-app 包中,该包使用此核心包并实现所有依赖。

目录

关于

Duktig 是一个轻量级的 MVC 微型 Web 框架,专为 PHP 7.1 编写。它最初被创建为一个教育项目,但它经过了全面测试,也可用于生产环境。它实现了 MVC 模式,并具有 IoC 容器、事件系统和 HTTP 中间件。

duktig-skeleton-web-app

duktig-skeleton-web-app 包是一个全功能项目,基于 duktig-core 包。它可以作为开发自己的 Duktig 框架应用程序的起点,因为它已根据流行的开源项目和包预先实现了所有必要的依赖。

目的

Duktig 框架的目标是提供一种灵活而强大的框架,通过使用最可行和最新的特性和实践来创建 Web 应用程序。

通过学习当今一些最受欢迎的 PHP Web 框架(AuraSilexSlimStackYii2LumenSymfonyLaravelBulletProton),Duktig 的核心架构依赖于现代原则和标准。

标准

  • PSR 兼容
    • PSR-2 编码和 PSR-4 自动加载标准
    • 接口:PSR-3 日志记录器,PSR-7 HTTP 消息,PSR-11 容器,PSR-15 HTTP 中间件,PSR-17 HTTP 工厂
  • 解耦的包设计
  • 由流行的开源项目和库提供支持
  • 通过 TDD 开发,单元测试、集成测试和功能测试

功能

  • MVC 模式
  • IoC 和 DI 容器
  • HTTP 中间件
  • 事件系统
  • 懒加载

包设计

大多数 Duktig 核心服务都与核心包解耦,并打包到自己的模块中。这种方法提供了高度的灵活性和良好的包设计。通过接口注入,对象图在执行过程中自然组合。

核心服务

duktig-core 实现了其自身的几个核心服务,同时将大多数其他服务的实现留给了外部项目

这些核心服务已在核心的 'Config/services.php' 文件中注册。如果需要,它们可以被覆盖并由您自己的配置替换。这是通过使用 'skipCoreServices' 配置参数实现的,在这种情况下,它们必须由您自己的配置指定。

duktig-core 默认使用 Auryn DI 容器,或者更确切地说,使用适配器包 duktig-auryn-adapter,该包简单地将其 API 适配到 Duktig 的 规范。容器可以被更改为您自己的选择。

要求

duktig-core 包定义并使用了一组需要在运行时由可解析服务实现的接口。duktig-skeleton-web-app 展示了在实际情况下如何实现这一点,并且是编写您自己的 Duktig 框架应用的推荐起点。简而言之,为了满足这些要求,一个应用程序(例如 duktig-skeleton-web-app)首先将所有必需的包作为 composer 依赖项 包含,然后 将它们注册为服务

一旦实现,应用程序就可以访问以下接口的实现

依赖注入

容器

DI 容器必须实现 Duktig\Core\DI\ContainerInterface。此接口是 Psr\Container\ContainerInterface 的扩展,并包含它自己的几个方法。Duktig 的依赖项解析基于构造函数参数注入,这是容器必须实现的核心功能。

默认情况下,Duktig 使用 Auryn 容器,或者更确切地说,使用适配器包 duktig-auryn-adapter,该包适配到定义的接口。这定义在 dutig-core配置 中。然而,容器可以被更改为任何实现 Duktig\Core\DI\ContainerInterface 的 PSR-11 容器。

ContainerFactory

容器本身由 Duktig\Core\DI\ContainerFactory 实例化和配置。如果自定义容器类有任何构造函数参数,则 ContainerFactory 将尝试使用 ReflectionClass 解析和注入它们。

然后,通过运行服务配置器来配置容器。服务在您的应用程序的 services.php 配置文件中配置。

依赖解析

与其他标准PHP依赖注入容器一样,构造函数参数类型提示用于提供依赖注入。以下框架中的实体由容器解析:

  • 服务
  • 控制器
  • 闭包类型路由处理器
  • 中间件
  • 事件监听器

这些实体将在运行时解析并注入其构造函数参数。任何依赖都可以通过这种方式注入,无论是之前已定义并作为服务添加到容器中,还是自动提供,这当然取决于您选择的容器是否支持自动提供功能(Auryn DI容器就支持)。

懒加载

框架本身利用了懒加载优化,并在几种情况下延迟对象创建,从而提高了性能。例如,控制器解析仅发生在中间件栈的末尾,而不会更早发生。懒加载自然与容器的make()方法实现相关。因此,如果您选择的容器使用懒加载(Auryn DI容器就是这样),它也将应用于框架的工作流程。

框架组件

路由

duktig-core根据几个实体定义其路由。它使用一个路由器,该路由器负责将当前请求匹配到适当的路由。它还使用一个路由提供者服务,该服务提供了一种简单的API来检索和识别可用的路由。

路由器

Duktig的路由器作为一个独立的服务。路由器必须实现Duktig\Core\Route\Router\RouterInterface。该接口定义了一个强制方法match,它将Psr\Http\Message\ServerRequestInterface对象匹配到Duktig\Core\Route\Router\RouterMatch对象。RouterMatch仅仅是一个值对象,表示匹配到的路由及其参数。

路由

Duktig\Core\Route\Route是Duktig的路由模型。其形式受到了Symfony Route的路由模型的强烈影响,因为它是在开源社区中最丰富和最受欢迎的路由表示之一。

路由提供者

Duktig\Core\Route\RouteProvider是一个服务,提供对路由的访问。它从配置服务中获取路由的配置,并将其转换为Route对象,通过几个用户友好的API方法暴露它们。

路由处理器

路由可以有两种不同类型的解析器:

  • 第一种是具有操作方法的经典控制器,其中控制器扩展了BaseController类,公开了对请求和一些基本组件的访问;
  • 第二种是直接在路由配置中给出的闭包类型处理器。

两种类型的路由处理器都必须返回一个ResponseInterface类型的对象。对于闭包类型处理器,建议使用Interop\Http\Factory\ResponseFactoryInterface来创建响应实例,而控制器将通过BaseController父类预先准备好响应以供使用。

两种类型的处理器都由容器动态解析,并在创建时注入其构造函数参数的依赖。

控制器

控制器分配给路由,并负责生成响应。或者,除了定义特殊的控制器类外,还可以使用更简单的闭包类型路由处理器

BaseController

所有控制器类应扩展基本类Duktig\Core\Controller\BaseController,以便访问应用程序上下文,包括属性

  • $request - PSR-7 请求对象
  • $response - 一个“新鲜”的 PSR-7 响应对象
  • $queryParams - 解析的 URI 参数
  • $renderer - 模板渲染服务
  • $config - 配置服务

BaseController还提供了用于更快速操作响应对象及其渲染的方法。

路由参数

路由参数作为动作方法的参数传递给控制器。例如,如果路由使用一个字符串参数$param,并将其分配给exampleAction方法,则该参数将以这种方式在动作方法中可用

public function exampleAction(string $param) : ResponseInterface;

返回类型

每个控制器或路由处理程序都必须返回一个 PSR-7 响应对象。对于扩展主BaseController类的控制器,可以使用$response属性,该属性由内部由 PSR-17 $responseFactory服务生成。

依赖注入

控制器的构造函数参数将在运行时解析和注入。控制器,以及其他实体,默认情况下不提供对容器的访问,因为这通常被视为服务定位器反模式。然而,也没有对此方法施加特殊限制,并且可以轻松实现。然而,这种做法强烈不建议。

可能没有必要特别指出这一点,但当然,当您的控制器定义它自己的依赖关系时,它还必须尊重其父级的依赖关系,即。

<?php
namespace MyProject\Controller;

use Duktig\Core\Controller\BaseController;
use Interop\Http\Factory\ResponseFactoryInterface;
use Duktig\Core\View\RendererInterface;
use Duktig\Core\Config\ConfigInterface;
use MyProject\Service\CustomService;

class IndexController extends BaseController
{
    private $customService;
    
    public function __construct(
        CustomService $customService,
        ResponseFactoryInterface $responseFactory,
        RendererInterface $renderer,
        ConfigInterface $config
    )
    {
        parent::__construct($responseFactory, $renderer, $config);
        $this->customService = $customService;
    }
}

延迟加载

控制器仅在命令链到达时解析和实例化。使用特殊的ControllerResponder中间件解析和执行控制器,并将响应返回给应用程序。

中间件

Duktig使用与PSR-15规范对应的“单遍”HTTP中间件。这意味着实现Psr\Http\ServerMiddleware\MiddlewareInterface以及具有以下签名的函数

public function process(ServerRequestInterface $request, DelegateInterface $delegate);

同样,中间件调度系统必须实现Psr\Http\ServerMiddleware\DelegateInterface以及以下方法

public function process(ServerRequestInterface $request);

Duktig从其核心功能中省略了中间件调度系统的实现,并将其委托给外部包。

应用程序和路由中间件

Duktig使用两种类型的中间件

  • 应用程序中间件 - 全局适用于整个应用程序,它在每个请求上运行
  • 路由中间件 - 可变,可以分配给特定路由

ControllerResponder

ControllerResponder是一个特殊的中间件,位于中间件堆栈的末尾。它解析路由处理程序,调用它,并将响应返回给中间件堆栈。由于它作为路由处理程序视角中的“响应者”使用,因此得名。

中间件堆栈

请求穿越的完整中间件堆栈由以下内容组成

  • 应用程序中间件
  • 路由中间件
  • ControllerResponder中间件

依赖注入

所有中间件都由容器实例化,因此将注入它们的构造函数依赖关系。

模板

模板渲染服务由Duktig\Core\View\RendererInterface定义。它提供了一个简单的API,用于使用模板。

事件

事件调度器

事件调度器由Duktig\Core\Event\Dispatcher\EventDispatcherInterface定义。这意味着向调度器提供一个容器,然后用于解析监听器。因此,事件监听器将在它们的事件被分发时进行懒加载。

事件

Duktig中的事件是简单的值对象,包含监听器执行所需的上下文信息。也可以说,事件只是一个具有唯一名称的值对象。

在Duktig中可以使用两种不同的事件类型。

事件作为其独立的类

可以将事件创建为其自己的类,该类必须扩展Duktig\Core\Event\EventAbstract类。

在这种情况下,其名称可以但不一定需要特别提供(作为构造函数参数),默认事件的名称将是其全限定类名,不带前缀反斜杠。例如,对于事件类MyProject\Event\CustomEvent,其默认名称将是'MyProject\Event\CustomEvent'

以下是一个简单示例,展示如何触发在其自己的独立类中定义的事件。假设UserEvent将参数$user作为构造函数参数。分发此事件就像这样

$event = new \DemoApp\Event\UserEvent($user);
$eventDispatcher->dispatch($event);

简单事件

对于最简单的事件,它仅由其唯一的名称表示,并且不需要为监听器的处理程序提供任何其他信息,在这种情况下,不必为事件编写单独的类,而是可以使用现有的Duktig\Core\Event\EventSimple类即时创建事件。

在这种情况下,必须将一个唯一的事件名称提供给构造函数。由于EventSimple可以用来实例化不同的事件,因此每个事件都负责其唯一的命名。

可以通过即时实例化EventSimple对象来分发简单事件,即

$eventDispatcher->dispatch(new \Duktig\Core\Event\EventSimple('theEventName'));  

监听器

事件监听器可以是可解析的类/服务名称或简单闭包。

在第一种情况下,如果监听器是一个单独的类,它必须实现Duktig\Core\Event\ListenerInterface。当事件被分发时,监听器将被容器解析,并且注入所有其构造函数依赖项。

如果监听器以简单闭包的形式提供,则它不会被容器解析,因此不会注入任何依赖项。闭包类型的监听器期望只有一个可选参数,即事件

function($event) { /* ... */ }

核心事件

框架在其应用流程的关键点上分发其核心事件。Duktig的核心事件列表可以在duktig-coreevents.php文件中找到。一些核心事件仅通过其唯一的名称定义(例如,'duktig.core.app.afterConfiguring'),而其他事件则是作为单独的类创建的。

错误处理

Duktig使用其自己的错误和异常处理器,该处理器实现了Duktig\Core\Exception\Handler\HandlerInterface。其基本任务是注册整个应用中的错误处理,将\Throwable转换为响应,并报告此类错误的发生。

它考虑了PHP 7的错误和异常处理机制。从PHP 7版本开始,\Error\Exception类都实现了\Throwable接口。现在,一些致命错误和可恢复错误会抛出异常,而不是停止脚本执行。未捕获的异常将继续产生致命错误,同样,从未捕获的致命错误抛出的\Error异常也会产生致命错误。

在生产环境中,异常和错误通过默认或自定义错误模板进行渲染。处理器根据其位置和名称对模板进行优先排序。它将搜索给定可抛出对象的最佳模板,首先尝试在应用自定义模板路径中定位模板,如果没有找到,则使用duktig-core包中的默认模板。它按照以下步骤搜索和渲染错误模板

  • 如果抛出HttpException,它将搜索具有错误代码名称的模板
  • 如果没有找到这样的模板,以及所有其他异常类型,它将寻找一个名称等于异常类名称的模板。
  • 最后,它将搜索一个通用的错误模板。

渲染服务本身被赋予了模板的位置,包括应用目录内的自定义模板和框架核心目录内的默认模板。这样,它首先寻找自定义模板,然后是默认模板。

配置

配置的详细信息在duktig-skeleton-web-app项目中进行了描述,在那里可以看到其实际应用。骨架应用程序采用了duktig-core,并为其提供了所有依赖项,将其应用于完整的Duktig环境中。

测试

duktig-core和由duktig-skeleton-web-app实现的所有包都使用PHPUnit和Mockery进行了全面测试。

针对测试环境,提供了一个特殊的Duktig\Test\AppTesting类。它扩展了对容器和响应对象的访问。它可以用来轻松地模拟服务,并获得对响应的直接访问。