peppers / app-base
目录结构和文件,用于开始构建 Peppers 网络应用程序
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
特别请求凭据时应使用哪些凭据,这是唯一的强制键值对。默认值必须与第一级键连接点(.)然后是第二级键名称对应。
在上面的例子中,第一级键指示外部数据源是一个 MySQL 数据库。第二级键指示数据库名称。示例中的键名称仅用于类比目的,您可以使用任何名称。第三级是凭据。
目前仅支持用户名和密码,这些键是强制性的。
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
特别请求数据源时应使用哪个数据源,这是唯一的强制键值对。默认值必须与第一级键连接点(.)然后是第二级键名称对应。在上面的例子中,第一级键对应于包装器(非限定)类名称,该类必须存在,用于访问数据源。第二级键指示数据库名称。第三级是 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路由系统使用正则表达式。在上面的示例中,您可以看到使用Route
辅助类声明的3个路由。这个类的函数是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请求,路径有两个参数 - {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
实例中的服务。服务Implementation
有两种形式:抽象和具体。
抽象
抽象实现允许开发者将接口绑定到ServiceLocator
。为了解析服务类,开发者需要设置一个提供者。这个提供者可以是一个返回所需服务类实例的Closure
实例,或者是一个指向返回所需服务类实例的Strategy
类的(完全限定)类名。还可以将实现绑定到特定类(array
,abstract()
方法的第二个参数),允许开发者在Closure
提供者中类型提示一个BoundTo
实例,然后决定如何解析服务类实例。
具体
具体实现是抽象实现的相反。它们不需要提供者,因此开发者必须提供将被ServiceLocator
解析的完全限定类名。这种类型的实现可以立即加载(不是延迟加载),而抽象实现则不行。
抽象和具体实现都支持依赖注入。如果需要,Factory
将使用ServiceLocator
注入必要的依赖。如果依赖项有依赖项,它们将以递归方式解析。
以下是一个Peppers应用程序中服务(s)注册的示例
// 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的核心服务(《内核》使用它记录信息)。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
;处理请求并发送响应回客户端所需的代码。这些类在管道中运行,第一个接收由RouteResolver
解析的RouteRegister
实例作为输入,从那时起,下一个接收前一个调用返回的输入。此处设置的类必须实现PipelineStage
合约;ExceptionHandling
;尝试处理未捕获的异常并向客户端提供有关异常的有意义信息的代码。此代码是“环境感知”的,这意味着:如果应用程序处于生产模式,则响应中显示的信息非常少,否则它显示所有异常信息。这些类在管道中运行,第一个接收未捕获的异常实例作为输入,从那时起,下一个接收前一个调用返回的输入。此处设置的类必须实现PipelineStage
合约;shutdown
;关闭框架所需的代码:触发延迟事件处理并在ServiceLocator
中注册的服务上调用shutdown()
方法。这些类不在管道中运行,因此Kernel
不提供任何输入。如果这些策略中的任何一个失败,则不会向客户端显示任何内容,因为此时Peppers完全“输出静默”,它只是在Kernel panic文件中记录。
注意1:不应修改键,它们在Kernel
中是硬编码的。注意2:任何未捕获的异常都记录在kernel panic文件中。检查index.php
中的Settings
类以获取其位置。
评论和建议
发送到peppers.php.framework@gmail.com
谢谢 :)