lucinda/mvc

PHP应用程序中通过MVC模式处理请求到响应的超高性能API


README

目录

关于

此API是一个(需要开发者绑定)的框架(用于高效处理Web请求到服务器响应),其中视图和模型应该是独立的,而控制器基于用户请求在两者之间进行调解。它以模块化、效率和简单为基础,既面向对象也面向事件:类似于JavaScript,它允许开发者在处理过程中绑定在预定义事件达到时将执行的逻辑。

diagram

API仅执行标准MVC逻辑,因此在实际生活中,它期望在顶部构建一个Web框架以添加更多功能(例如:数据库连接)。为了使用它,开发者需要执行以下步骤

  • 配置:设置一个XML文件,其中包含此API的配置
  • 绑定点:将用户在XML/代码中定义的组件绑定到API原型中,以获得必要的功能
  • 初始化:实例化FrontController,一个Runnable,可以在处理过程中根据上述内容处理请求到响应
  • 绑定事件:设置将在处理过程中达到预定义事件时实例化和运行Runnable
  • 配置共享变量:扩展Attributes类,以封装特定于项目的变量,以便在事件监听器和控制器之间共享
  • 处理:在FrontController上调用run方法,最终处理请求到响应,触发上述事件(如果有任何)

API完全符合PSR-4标准,仅需要抽象MVC API来执行基本MVC逻辑,PHP8.1+解释器和SimpleXML扩展。要快速了解其工作原理,请查看

  • 安装:根据上述步骤,描述如何在您的计算机上安装API
  • 参考指南:描述了所有与开发人员相关的API类、方法和字段
  • 单元测试:API具有100%的单元测试覆盖率,使用UnitTest API代替PHPUnit以获得更大的灵活性
  • 示例:基于FrontController单元测试的API功能深入示例

内部所有类都属于 Lucinda\STDOUT 命名空间!

配置

要配置此API,您必须有一个包含以下标签的XML文件

  • 应用:(必填)在一般基础上配置您的应用
  • 解析器:(必填)配置您的应用能够解析响应的格式
  • 路由:(必填)配置将请求资源绑定到控制器和视图的路由
  • 会话:(可选)配置在创建会话时自动使用选项
  • Cookie:(可选)配置在设置Cookie时自动使用的选项

应用

标签文档完全由继承的抽象MVC API 规范 覆盖!由于此API的STDIN由HTTP(s)请求组成,因此 default_route 属性的值必须指向 index(主页)以处理不带路由的请求。

解析器

标签文档完全由继承的抽象MVC API 规范 覆盖!

路由

此标签的最大语法是

<routes>
    <route id="..." controller="..." view="..." format="..." method="...">
        <parameter name="..." validator="..." mandatory="..."/>
        ...
    </route>
    ...
</routes>

大多数标签逻辑已由抽象MVC API 规范 覆盖。以下额外观察需要指出

  • id:(必填)请求资源URL(不带尾随斜杠)。可以是精确的URL(例如:foo/bar)或URL模式(例如:user/(id))。如果使用模式,每个变量都必须命名并放在括号内!
  • controller:(可选)用户定义的PS-4自动加载兼容类(包括命名空间)的名称,该类将基于模型缓解请求和响应
    类必须是 控制器 实例!
  • method:(可选)持有请求资源时必须使用的单个HTTP方法。如果请求使用不同的方法,则会抛出 MethodNotAllowedException

标签示例

<routes>
    <route id="index" controller="Lucinda\Project\Controllers\Homepage" view="index"/>
    <route id="user/(id)" controller="Lucinda\Project\Controllers\UserInfo" view="user-info">
</routes>

^ 在 应用 XML标签中,必须定义由 default_route 属性定义的路由!

如果请求没有路由,则使用 默认 路由。但是,如果请求具有不匹配 id 的路由,则抛出 PathNotFoundException

路由参数

每个 route 标签可以包含一个或多个规则,以验证请求和路径参数的值,这些参数随请求一起发送。每个参数对应一个 parameter 标签,验证可以根据属性进行配置

  • name:(必填)您想要验证的请求或路径参数的名称。示例
    • foo,如果请求是GET并且带有查询字符串 ?foo=bar
    • id,如果路由URL是 user/(id)
  • validator:(必填) 用户自定义的PS-4自动加载兼容类(包括命名空间)的名称,该类将验证参数的值。
    必须是EventListeners\Validators\ParameterValidator 实例!
  • mandatory:(可选) 表示参数是否必填(值可以是0或1)。如果没有指定,则默认为必填(1)!

^ 如果参数名称冲突,则路径参数优先于请求参数!

标签示例

<routes>
    <route id="index" controller="Lucinda\Project\Controllers\Homepage" view="index"/>
    <route id="user/(id)" controller="Lucinda\Project\Controllers\UserInfo" view="user-info" method="GET">
        <parameter name="id" validator="Lucinda\Project\ParameterValidators\UserNameValidator"/>
    </route>
</routes>

会话

此标签的最大语法是

<session save_path="..." name="..." expired_time="..." expired_on_close="..." https_only="..." headers_only="..." referrer_check="..." handler="..." auto_start="...">

位置

  • save_path:(可选) 服务器上保存会话的绝对路径。例如:"/tmp/sessions/"
  • name:(可选) 用于作为cookie名称的会话名称(默认:PHPSESSID)。例如:"SESSID"
  • expired_time:(可选) 数据将被垃圾回收后的秒数。例如:"60"
  • expired_on_close:(可选) 预期会话cookie在客户端浏览器关闭后存活的秒数。例如:"120"
  • https_only:(可选) 将会话cookie标记为仅可通过安全的HTTPS连接访问。值:"1"
  • headers_only:(可选) 将会话cookie标记为仅可通过HTTP协议访问。值:"1"
  • referrer_check:(可选) 您想在每个HTTP Referer中检查的子字符串,以验证会话cookie。例如:"Chrome"
  • handler:(可选) 实现了\SessionHandlerInterface 的用户自定义PS-4自动加载兼容类(包括命名空间)的名称。例如:"application/models/RedisHandler"
  • auto_start:(可选) 表示会话将自动在每个请求中启动。值:"1"

标签示例

<session save_path="/tmp/sessions/" name="SESSID" expired_time="60" expired_on_close="120" https_only="1" headers_only="1" referrer_check="Chrome" handler="application/models/RedisHandler" auto_start="1">

Cookie

此标签的最大语法是

<cookies path="..." domain="..." https_only="..." headers_only="...">

位置

  • path:(可选) 服务器上cookie可用的路径。例如:"/foo/"
  • domain:(可选) cookie可用的(子)域名。例如:"www.example.com"
  • https_only:(可选) 表示cookie应仅通过客户端的安全HTTPS连接传输。值:"1"
  • headers_only:(可选) 表示cookie应仅通过HTTP协议访问。值:"1"

标签示例

<cookies path="/" domain="example.com" https_only="1" headers_only="1">

要了解在需要时如何正确设置 pathdomain,请检查规范

绑定点

为了保持灵活性和实现最高性能,API不假设超过绝对必要的任何内容!相反,它为开发者提供了将其原型绑定以获得一定功能的能力。

声明式绑定

它为开发者提供了通过XML 声明式地绑定到其原型类/接口的能力。

程序式绑定

它为开发者提供了通过FrontController 构造函数 程序式地绑定到其原型的能力

和addEventListener方法(请参阅绑定事件部分)!

执行

初始化

开发者为配置API的XML设置完成后,他们最终可以通过实例化FrontController 来初始化它。

除了实现Runnable 接口所需的 run 方法外,类还提供了以下公共方法

位置

  • $documentDescriptor:XML 配置 文件的相对位置。例如:"configuration.xml"
  • $attributes:请参阅 配置共享变量
  • $type:事件类型(参见下文的绑定事件),由枚举EventType封装。
  • $className:监听器类名,包括命名空间和子文件夹,在实例化属性时定义的文件夹中找到。

示例

$handler = new FrontController("configuration.xml", new MyCustomAttributes("application/event_listeners");
$handler->run();

绑定事件

如上所述,API允许开发人员通过addEventListener方法将监听器绑定到处理生命周期事件。每个EventType对应一个抽象的Runnable类。

监听器必须扩展相应的事件类并实现必要的run方法,该方法包含在事件被触发时执行的逻辑。它们必须在run方法运行之前进行注册。

$handler = new FrontController("stdout.xml", new FrameworkAttributes("application/listeners");
$handler->addEventListener(EventType::APPLICATION, Lucinda\Project\EventListeners\Logging::class);
$handler->addEventListener(EventType::REQUEST, Lucinda\Project\EventListeners\Security::class);
$handler->run();

要了解如何定位事件监听器,请查看规范

配置共享变量

API允许事件监听器设置将要提供给后续事件监听器和控制器的变量。对于每个变量都有一个

  • setter:由事件监听器运行一次
  • getter:由后续事件监听器和控制器运行

API附带Attributes,它包含每个站点都必须扩展以设置其自己的变量的基础。除非您的网站非常简单,否则将需要开发人员扩展此类并添加更多变量,为此必须定义setter和getter!

处理

完成上述步骤后,开发人员最终可以通过FrontControllerrun方法来处理请求到响应,该控制器

所有由开发者负责的组件(控制器Lucinda\MVC\ViewResolver,以及事件监听器本身)都实现了Runnable接口。

安装

首先选择一个文件夹,然后在控制台该文件夹中使用以下命令

composer require lucinda/mvc

将上述文件夹重命名为DESTINATION_FOLDER,然后在其中创建一个包含以下内容的.htaccess文件

RewriteEngine on
Options -Indexes
ErrorDocument 404 default
RewriteCond %{REQUEST_URI} !^/public
RewriteRule ^(.*)$ index.php

然后在项目根目录下创建一个包含以下代码的configuration.xml配置文件和一个index.php初始化文件(参见上述初始化

$controller = new Lucinda\STDOUT\FrontController("configuration.xml", new Attributes("application/events"));
// TODO: add event listeners here
$controller->run();

单元测试

有关测试和示例,请检查API源中的以下文件/文件夹

  • test.php:在控制台运行单元测试
  • unit-tests.xml:设置单元测试和模拟"loggers"标签
  • tests:从src文件夹中的类进行的单元测试

参考指南

这些类完全由API实现

  • Application:读取配置XML文件并封装内部信息
  • Request:基于超全局变量$_SERVER、$_POST、$_GET中的信息封装用户请求
  • Session:封装与映射到$_SESSION超全局变量的http会话操作
  • Cookies:封装与映射到$_COOKIE超全局变量的http cookie操作

除了在绑定事件中提到的类之外,以下抽象类需要由开发者扩展以获得功能

Application 类

Application类封装从XML中检测到的信息,并定义了以下与开发者相关的公共方法

Request 类

Request封装了基于超全局变量($_SERVER, $_GET, $_POST, $_FILES)检测到的用户请求信息,并为开发者定义了以下相关公共方法:

类 Request Client

Request\Client封装了基于超全局变量$_SERVER检测到的客户端信息,并为开发者定义了以下相关公共方法:

类 Request Server

Request\Server封装了基于超全局变量$_SERVER检测到的Web服务器信息,并为开发者定义了以下相关公共方法:

类 Request URI

Request\URI封装了基于超全局变量$_SERVER检测到的路径信息,并为开发者定义了以下相关公共方法:

要了解这个类如何处理请求的URI,请查看规格说明

类 Request UploadedFile

Request\UploadedFiles\File封装了基于超全局变量$_FILES的单个上传文件信息,并为开发者定义了以下相关公共方法:

为了处理上传的文件,为开发者添加了两种方法。

要了解这个类如何处理上传的文件,请查看规格说明

类 Session

Session封装了通过$_SESSION超全局变量执行HTTP会话操作的方法,并为开发者定义了以下相关公共方法,所有这些都适用于开发者:

类 Session Cookie

Session\Cookie封装了通过HTTP会话cookie执行操作的方法,并为开发者定义了以下相关公共方法,所有这些都适用于开发者:

类 Cookies

Cookies封装了通过$_COOKIE超全局变量执行HTTP cookie操作的方法,并为开发者定义了以下相关公共方法,所有这些都适用于开发者:

接口 ParameterValidator

接口EventListeners\Validators\ParameterValidator实现了通过方法进行单个请求参数值验证的蓝图。

一个验证作为参数接收的用户名称的类的示例(例如:/user/(name)路由)

class UserNameValidator implements Lucinda\STDOUT\EventListeners\Validators\ParameterValidator
{
    public function validate($value)
    {
        $result = DB("SELECT id FROM users WHERE name=:name", [":name"=>$value])->toValue();
        return ($result?$result:null);
    }
}

要了解参数如何工作,请查看规格说明标签文档

抽象类 EventListeners Start

抽象类EventListeners\Start实现了Runnable接口,并监听在读取配置 XML之前执行的事件。

开发者需要实现一个run方法,在该方法中,他们可以通过构造函数访问API注入的以下受保护字段:

START监听器的一个常见示例是设置开始时间,以便稍后基准测试处理持续时间。

class StartBenchmark extends Lucinda\STDOUT\EventListeners\Start
{
    public function run(): void
    {
        // you will first need to extend Application and add: setStartTime, getStartTime
        $this->attributes->setStartTime(microtime(true));
    }
}

抽象类 EventListeners Application

抽象类 EventListeners\Application 实现了 Runnable) 并监听在读取 配置 XML 之后执行的的事件。

开发者需要实现一个run方法,在该方法中,他们可以通过构造函数访问API注入的以下受保护字段:

使用示例

https://github.com/aherne/lucinda-framework/blob/master/src/EventListeners/SQLDataSource.php

抽象类 EventListeners 请求

抽象类 EventListeners\Request 实现了 Runnable) 并监听在 RequestSessionCookies 对象创建之后执行的事件。

开发者需要实现一个run方法,在该方法中,他们可以通过构造函数访问API注入的以下受保护字段:

使用示例

https://github.com/aherne/lucinda-framework/blob/master/src/EventListeners/Security.php

抽象类 EventListeners 响应

抽象类 EventListeners\Response 实现了 Runnable) 并监听在 Lucinda\MVC\Response 体被设置后、但在提交回调用者之前执行的事件。

开发者需要实现一个run方法,在该方法中,他们可以通过构造函数访问API注入的以下受保护字段:

使用示例

https://github.com/aherne/lucinda-framework/blob/master/src/EventListeners/HttpCaching.php

抽象类 EventListeners 结束

抽象类 EventListeners\End 实现了 Runnable) 并监听在 Lucinda\MVC\Response 被渲染回调用者之后执行的事件。

开发者需要实现一个run方法,在该方法中,他们可以通过构造函数访问API注入的以下受保护字段:

START 监听器的一个常见示例是设置结束时间,以便衡量处理持续时间。

class EndBenchmark extends Lucinda\STDOUT\EventListeners\End
{
    public function run(): void
    {
        $benchmark = new Benchmark();
        $benchmark->save($this->attributes->getStartTime(), microtime(true));
    }
}

抽象类 控制器

抽象类 Controller 实现了 Runnable),通过将先前检测到的信息绑定到模型来设置响应(特别是视图)。它定义了以下对开发者相关的公共方法

开发者需要为每个控制器实现 run 方法,其中他们可以访问通过构造函数由 API 注入的以下受保护字段

使用示例

https://github.com/aherne/lucinda-framework-configurer/blob/master/files/controllers/no_rest/IndexController.php

要了解更多关于控制器如何被检测的信息,请查看 规范

类属性

Attributes 封装了在整个请求-响应周期中收集的数据,每个对应一个 getter 和 setter,并可供后续事件监听器或控制器使用。API 已经包含了以下

收集的大部分数据都需要开发者自己设置以适应他们的项目需求,因此,在 99% 的情况下,每个项目都需要扩展此类!使用示例

https://github.com/aherne/lucinda-framework/blob/master/src/Attributes.php

规范

由于此 API 在 Abstract MVC API 规范之上工作,因此它遵循其要求并添加了一些额外的要求

如何检测响应格式

本节遵循父 API 规范,只是路由是根据 $_SERVER["REQUEST_URI"] 的值进行检测的。

如何定位视图解析器

本节完全遵循父 API 规范

如何检测路由

本节遵循父 API 规范,只是路由是根据 $_SERVER["REQUEST_URI"] 的值进行检测的。以下是一个 XML 示例

<application default_route="index" ...>
	...
</application>
<routes>
    <route id="index" .../>
    <route id="users" .../>
    <route id="user/(id)" .../>
</routes>

以上将出现以下情况

如何定位控制器

本节遵循父API 规范,只是定义为 路由 标签中的 controller 属性的类必须扩展 Controller

参数验证器是如何工作的

为了更好地理解 application XML 标签中的 validators 属性如何与 routes 标签中的 parameter 子标签一起工作,以便根据传入的请求定位要运行的验证器,让我们以这个XML为例

<routes>
    <route id="users/(uname)" method="GET" ...>
    	<parameter name="uname" class="Lucinda\Project\ParameterValidators\UserName"/>
    </route>
    <route id="user/info" method="POST" ...>
    	<parameter name="id" class="Lucinda\Project\ParameterValidators\UserId"/>
    	<parameter name="name" class="Lucinda\Project\ParameterValidators\UserName" mandatory="0"/>
    </route>
    ...
</routes>

收到对 /users/aherne 的请求时,API将

  • 检测到ID为 users/(uname) 的路由和接收到的请求参数(路径参数、GET、POST)
  • 检查是否使用GET方法调用路由。如果不是,将抛出 MethodNotAllowedException 异常!
  • 实例化 Lucinda\Project\ParameterValidators\UserName 并在“uname”路径参数的值上运行 validate 方法。如果未发送参数或验证失败,将抛出 ValidationFailedException 异常!

收到对 /users/info 的请求时,API将

  • 检测到ID为 user/info 的路由和接收到的请求参数(路径参数、GET、POST)
  • 检查是否使用POST方法调用路由。如果不是,将抛出 MethodNotAllowedException 异常!
  • 实例化 Lucinda\Project\ParameterValidators\UserId 并在请求参数“id”的值上运行 validate。如果未发送参数或验证失败,将抛出 ValidationFailedException 异常!
  • 实例化 Lucinda\Project\ParameterValidators\UserName 并在请求参数“name”的值上运行 validate。如果发送了参数且验证失败,将抛出 ValidationFailedException 异常!

所有参数验证器都需要遵守PSR-4自动加载规范并实现 EventListeners\Validators\ParameterValidator 接口!

如何设置Cookies的路径和域名

下表显示了 cookies XML 标签中 path 属性的效果

下表显示了 cookies XML 标签中 domain 属性的效果

如何处理上传的文件

与 $_FILES 超全局变量不同,API保留了由 $_GET 或 $_POST 发送的表单结构,因此

<input type="file" name="asd[fgh]"/>

一旦发布,uploadedFiles 方法将返回

[
  "asd"=>["fgh"=>object]
]

其中 object 是一个 Request\UploadedFiles\File。要检索上传的文件,请使用 Request 中的 uploadedFiles 方法!

如何处理请求的URI

API根据以下算法将请求URI(REQUEST_URI参数的值@$_SERVER)分解成相关组件

  • 首先通过从DOCUMENT_ROOT中删除SCRIPT_FILENAME来检测 上下文路径。通常,上下文路径为空,因为网站部署到特定的主机名,但在某些情况下,它们直接部署在 localhost 上,因此当REQUEST_URI是 https:///mySite/foo/bar 时,上下文路径将是 mySite
  • 它从REQUEST_URI中删除上下文路径和QUERY_STRING,以便检测 请求的页面。如果没有指定特定的页面,如 http://www.example.com,则假定主页,因此请求的页面将为空字符串
  • 它根据QUERY_STRING参数记录查询字符串及其数组表示形式,基于@ $_SERVER。当收到非GET请求时,记录查询字符串参数的数组表示形式,而与Request类的parameters方法返回的数组表示形式分开是合理的。

示例

如何定位视图

本节完全遵循父API 规范。由于它取决于视图的解析类型,因此扩展尚未确定!