peppers / example-app
展示Peppers PHP框架核心功能及其使用方法的示例应用程序
Requires
- php: >=8.1
- peppers/framework: >=v1.0
README
Peppers需要一组固定的目录和文件才能正常运行。这些文件存储路由和服务描述符以及其他信息。下面提供这些信息以及Peppers内部工作原理的简要介绍。
应用程序基础
以下目录结构和文件是构建和运行Peppers应用程序所必需的
配置文件及其内容
简要介绍启动/运行Peppers应用程序所需的数据结构
credentials.php
此文件包含Peppers应用程序已知并需要访问外部数据源的身份验证信息。身份验证信息存储在CredentialStore
实例中(然后作为单例存储在ServiceLocator
中)。此文件包含以下关联数组
return [
'default' => 'mysql.peppers',
'mysql' => [
'peppers' => [
'user' => 'peppers',
'password' => 'peppers'
]
]
];
default
键指定在没有特别请求CredentialStore
的情况下应使用哪些身份验证信息,这是唯一的必填键值对。默认值必须对应于1级键名连接点(.)然后是2级键名。
在上面的示例中,1级键指示外部数据源是MySQL数据库。2级键指示数据库名称。示例中的键名仅用于类比目的,您可以使用任何名称。3级由身份验证信息组成。
目前仅支持用户名和密码,这些键是必需的。
datasources.php
此文件包含Peppers应用程序已知并可通过ConnectionManager
实例(然后作为单例存储在ServiceLocator
中)访问的外部数据源列表。此文件包含以下关联数组
return [
'default' => 'pdo.peppers',
'pdo' => [
'peppers' => [
'dsn' => 'mysql:host=172.17.0.3;port=3306;dbname=peppers;charset=utf8mb4',
]
],
];
default
键指定在没有特别请求ConnectionManager
的情况下应使用哪个数据源,这是唯一的必填键值对。默认值必须对应于1级键名连接点(.)然后是2级键名。在上面的示例中,1级键对应于包装器(未限定)类名,必须存在,用于访问数据源。2级键指示数据库名称。3级是DSN,对于所有定义的数据源,此键是必需的。
目前仅支持pdo
包装器。
eventshandlers.php
此文件包含事件和事件处理类之间的映射。此文件包含以下关联数组
use App\Events as E;
use App\Events\Handlers as H;
return [
E\BuildRoutesCacheFileEvent::class => [
H\BuildRoutesCacheFileHandler::class,
],
E\LogRequestEvent::class => [
H\LogRequestHandler::class,
],
];
上述是任何Peppers应用程序都提供的基线文件。数组键是表示事件的(完全限定)类的名称,值是接收事件进行处理的(完全限定)类名的数组。事件可以从应用程序的任何地方启动,并实时(阻塞)或响应发送后(延迟)处理。
事件及其映射存储在EventStore
实例中(作为单例存储在ServiceLocator
中)。
routes.php
此文件包含路由到控制器的映射。映射形式为RouteRegister
数组。您还可以使用辅助类Route
,它具有更多描述性方法,结果相同,如以下示例所示
use Peppers\Helpers\Http\Route;
return [
Route::get(
'/example/{name}/{phone}',
[App\Controllers\OneExampleController::class, 'nameOrSurname']
)->setPathExpression('name', '[a-z]+')
->setPathExpression('phone', '\d+')
->setQueryExpression('whatever', 'blabla')
->setAllowFreeQuery(true),
Route::post(
'/example/person',
[App\Controllers\AnotherController::class, 'newPerson']
)->setAllowFreeQuery(false),
Route::restful('/model/peppers', App\Models\Repositories\PeppersRepository::class)
];
Peppers 路由系统使用正则表达式。在上述示例中,您可以看到声明了3条路由,使用了 Route
辅助类。这个类的 方法是 Peppers 允许的 HTTP 方法名称,以及一个特殊的 restful()
方法。它们接收
- URL 路径,可能包含
{占位符}
用于变量值;您还可以传递查询; - 其中之一为
array|Closure|string
。如果您发送- 一个数组,第一个值是控制器类的(完全限定)名称,第二个是要调用的方法的名称;
- PHP 的
Closure
,并使用其中的任何类,这些类必须使用完全限定名称声明。闭包必须始终返回一个Response
实例; - 一个字符串,这个字符串必须是实现了
ModelRepository
合约的类的完全限定名称。如果是这种情况,并且- 该路由是为 GET 请求,要返回特定的模型实例,将模型主键列作为路径参数(...不要忘记对应的正则表达式以匹配),否则必须发送带有与模型列匹配的参数的查询,以便在数据源上执行更通用的查询,客户端会收到一个模型集合作为响应;
- 该路由是为 DELETE 请求,URL 路径必须包含主键作为路径参数,否则不会发生任何事情。Peppers 只允许使用主键值来删除模型;
- 该路由是为 POST 请求,将模型主键列作为路径参数(...不要忘记对应的正则表达式以匹配)以更新特定的模型实例(...模型数据从请求体中读取)。如果请求路径中没有设置主键参数,Peppers 将请求解释为创建新的模型实例,并从请求体中读取模型数据。
方法调用的返回值是一个 RouteRegister
实例,这允许设置路径参数和查询参数的正则表达式。
* 此方法返回一个 RestfulRouteRegister
实例,在路由解析过程中扩展为 n 个 RouteRegister
实例,每个允许的 HTTP 方法一个。
回到上面的示例,第一个 RouteRegister
实例代表一个 HTTP GET 请求,路径有 2 个参数 - {name}
和 {phone}
- 必须使用 setPathExpression()
设置正则表达式。该请求的 URL 必须有一个查询,该查询通过 setQueryExpression()
设置,必须对应于 whatever=blabla
。调用 setAllowFreeQuery()
允许客户端发送更多查询参数,对于这些参数,开发人员不必编写正则表达式。该请求由一个 OneExampleController
的 nameOrSurname()
方法处理。第二个 RouteRegister
实例代表一个没有动态路径参数且无法查询的 HTTP POST 请求,由一个 AnotherController
实例的 newPerson()
方法处理。第三个 Route
实例是不同的,在其最基本的形式中,您只需要声明 URL 路径,在路由解析过程中,这个 RouteRegister
扩展为 n 个自己,但增加了主键列作为 DELETE、GET 和 POST 请求的路径参数以及简化的形式。
您可以根据需要添加任意多的 Route
实例。如果没有匹配发生,Peppers 将发送 404 错误给用户代理,或使用默认控制器来显示您的自定义 404 页面或运行其他自定义业务逻辑。
最后但同样重要的是...
在所有处理器情况下,请求处理代码必须返回一个 Response
或 ResponseSent
实例。第一种情况允许根据请求的 Accept
HTTP 头正确格式化响应数据。第二种情况绕过了所有这些,意味着这是开发人员的责任:输出缓冲区刷新、设置 header()
等。
services.php
该文件存储了Peppers所知的所有服务,并存储在ServiceLocator
实例中。服务的实现有2种形式:抽象和具体。
抽象
抽象实现允许开发者将接口绑定到ServiceLocator
。为了解析服务类,开发者需要设置一个提供者。该提供者可以是返回所需服务类实例的Closure
实例,或者是一个指向返回所需服务类实例的Strategy
类的(完全限定)类名。也可以将实现绑定到特定类(array
,abstract()
方法的第二个参数),这样开发者就可以在Closure
提供者中为BoundTo
实例提供类型提示,并决定如何解析服务类实例。
具体
具体实现是抽象实现的相反。它们不需要提供者,因此开发者必须提供完全限定的类名,该类名将由ServiceLocator
解析。这种类型的实现可以立即加载(不是延迟加载),与抽象实现相反。
抽象和具体实现都支持依赖注入。如果需要,Factory
将使用ServiceLocator
注入必要的依赖。如果依赖项有依赖项,它们将以递归方式解决。
以下是一个Peppers应用程序服务注册的示例
// use Peppers\Helpers\Http\Request\BodyParameter;
// use Peppers\Helpers\Http\Request\QueryParameter;
// use Peppers\Helpers\Http\Request\PathParameter;
use Peppers\Helpers\Service\BoundTo;
use Peppers\Helpers\Service\Implementation as Imp;
use Peppers\Services;
use Peppers\Contracts;
use App\Contracts\HelpfulHelper;
return [
Imp::abstract(Contracts\RouteResolver::class)
->setProvider(Peppers\Strategy\Boot\RouteResolver::class)
->setIsSingleton(true),
Imp::concrete(Services\RequestBody::class)
->setIsLazyLoad(true)
->setIsSingleton(true),
Imp::abstract(Contracts\EventStore::class)
->setProvider(\Peppers\Strategy\Boot\EventStore::class)
->setIsSingleton(true),
Imp::abstract(Contracts\CredentialStore::class)
->setProvider(Peppers\Strategy\Boot\CredentialStore::class)
->setIsSingleton(true),
Imp::abstract(Contracts\ConnectionManager::class)
->setProvider(Peppers\Strategy\Boot\ConnectionManager::class)
->setIsSingleton(true),
Imp::abstract(
App\Contracts\HelpfulHelper::class,
[App\Controllers\ExampleController::class]
)->setProvider(function (BoundTo $caller): HelpfulHelper {
return $caller->getName() == App\Controllers\ExampleController::class
? new App\Helpers\AnotherHelpfulHelper()
: new App\Helpers\OneHelpfulHelper();
})
];
如上例所示,有6个服务被描述
RouteResolver
;它是一个抽象实现,因此开发者可以选择构建不同的一个(注意合同!)。由于它是抽象的,因此有一个提供者执行自定义逻辑并返回一个RouteResolver
实例。它还设置为单例,因此每次开发者调用ServiceLocator
时,都返回相同的实例。这是Peppers的核心服务之一。RequestBody
;它表示请求正文,设置为单例和延迟加载,因此仅在需要时才实例化(这发生在Factory
和ServiceLocator
之间)。这是Peppers的核心服务之一。注意:此服务处理请求正文编码,因此开发者不必这样做。它不打算直接使用。如果开发者需要访问正文数据,应使用BodyParameter
实例。EventStore
;作为延迟事件存储库,并将事件分发给已注册的处理程序。这是Peppers的核心服务之一(Kernel
使用它记录信息)。CredentialStore
;凭证存储文件中存储的凭证存储在这里。它由ConnectionManager
服务使用。这是Peppers的核心服务之一。ConnectionManager
;作为外部数据源访问数据的存储库,以及存储实际连接。这是Peppers的核心服务之一。- 这是将接口绑定到
ServiceLocator
并绑定到特定类的示例。在解析服务类型提示时,使用BoundTo $caller
,这样调用者类名就可以在解析服务实例类时的Closure
内部使用。在这种情况下,如果调用者是App\Controllers\ExampleController::class
,则注入/返回一个App\Helpers\AnotherHelpfulHelper
实例;否则是App\Helpers\OneHelpfulHelper
。
最后但同样重要的是...
服务是开发者根据其需求设计的类,Peppers没有设定具体要求。如果开发者需要在将响应发送给客户端后运行一些业务逻辑,则实现一个shutdown()
方法。它将在最后调用。请注意,如果此代码需要其他服务,则该服务可能不再可用!
提供者类只有一个要求:它们必须扩展 Strategy
基类。Strategy
基类的类允许失败,这意味着如果提供者无法完成其业务(返回的不是预期的结果)并且允许失败,则 ServiceLocator
会向调用者返回一个 StrategyFail
实例;如果不抛出 StrategyFail
异常,则必须在某处捕获它,最终由 Kernel
捕获,这将停止所有请求处理(如果处于启动阶段之外,Peppers 的关闭仍然会按正常方式发生)。
strategies.php
该文件包含将 Kernel
阶段映射到每个阶段应运行的代码的映射。以下是一个标准 Peppers 应用的示例
use Peppers\Strategy;
use Peppers\Strategy\Boot;
use Peppers\Strategy\Response;
return [
'boot' => [/* these classes are NOT run in a pipeline */
Boot\ServiceLocator::class,
Boot\SessionStart::class
],
'requestResponse' => [/* these classes are run in a pipeline */
Strategy\RouteToHandler::class,
Response\Html::class,
Response\Json::class,
Response\Redirect::class,
Response\NoBody::class,
Response\File::class,
Response\PlainText::class,
Response\Xml::class
],
'exceptionHandling' => [/* these classes are run in a pipeline */
Strategy\ResolveException::class,
Response\Html::class,
Response\Json::class,
Response\PlainText::class,
Response\Xml::class
],
'shutdown' => [/* these classes are NOT run in a pipeline */
Strategy\ShutdownServices::class,
]
];
上述示例中,键
boot
;启动框架所需的代码。这些类不会在管道中运行,因此Kernel
不提供任何输入,只是检查返回值是否符合预期或策略失败。任何失败都会完全停止请求处理!requestResponse
;处理请求并将响应发送给客户端所需的代码。这些类在管道中运行,第一个接收一个RouteRegister
实例作为输入(由RouteResolver
解析),从那时起,下一个接收上一个调用的返回值作为输入。这里设置的类必须实现PipelineStage
合约;ExceptionHandling
;尝试处理未捕获的异常并向客户端提供有意义的信息的代码。此代码是“环境感知”的,意味着:如果应用程序处于生产模式,则响应中显示的信息非常少,否则显示所有异常信息。这些类在管道中运行,第一个接收一个未捕获的异常实例作为输入,从那时起,下一个接收上一个调用的返回值作为输入。这里设置的类必须实现PipelineStage
合约;shutdown
;关闭框架所需的代码:触发延迟事件处理并调用在ServiceLocator
中注册的服务上的shutdown()
方法。这些类不会在管道中运行,因此Kernel
不提供任何输入。如果这些策略中的任何一个失败,客户端将不会看到任何内容,因为此时 Peppers 完全“无输出”,它只是在 Kernel 紧急文件中记录。
注意 1:不应修改键,它们在 Kernel
中是硬编码的。注意 2:任何未捕获的异常都会记录在内核紧急文件中。检查 index.php
中的 Settings
类以获取其位置。
注释和建议
发送到 peppers.php.framework@gmail.com
谢谢 :)