rloris / layer
Layer 是一个使用注解的面向对象的 MVC PHP 框架
Requires
- php: >=7.0
- ext-json: *
This package is auto-updated.
Last update: 2024-09-08 19:36:51 UTC
README
Layer 是一个面向对象的 MVC PHP 框架
此项目目前处于 alpha 阶段,您仍然可以尝试它并给我反馈
MVC 代表模型-视图-控制器
模型表示要操作的数据,视图表示数据的显示方式,控制器处理应用程序的业务逻辑。
-
模型通过简单的类定义
-
视图通过包含 HTML 内容的 .php 文件定义
-
控制器通过类定义
-
操作通过控制器内部的方法定义
您可以使用 Layer 轻松创建 API 或网站
-
网站使用视图来显示内容并允许用户与之交互(MVC)
-
API 不使用视图来显示数据,它以特定格式(如 JSON、XML、文本等)返回数据
-
您可以定义自定义错误控制器以用于您的网站或 API
入门
Composer
在项目初始化后,运行此命令以从 master 获取最新版本
composer require rloris/layer:dev-master
如果您更新了 layer,请删除生成的文件(map.json、routes.json、hash.json),以获取最新更改
配置
首先,我们需要设置 configuration.json 文件,它应该看起来像这样,这是最小设置
{
"locations": {
"controllers": "{path_to_the_folder_containing_your_controllers}",
"shared": "{path_to_the_folder_containing_your_shared_components}",
"build": "{path_to_the_folder_for_layer_output_build_files}",
"log": "{path_to_the_folder_for_layer_output_log_files}"
},
"environment": {
"current": "dev",
"dev": {
"routeTemplate" : "",
"apiRouteTemplate" : "api",
"log": false,
"logTemplate" : "[{request_datetime}][{environment}][{request_method} {request_resource}]:{message}",
"build": true
},
"prod": {
"routeTemplate" : "",
"apiRouteTemplate" : "api",
"log": true,
"logTemplate" : "[{request_datetime}][{environment}][{client_ip} {client_browser} {client_os}][{request_method} {request_resource}]:{message}",
"build": false
},
"{your_other_env_name}": {
"routeTemplate" : "",
"apiRouteTemplate" : "api",
"log": false,
"logTemplate" : "[{request_datetime}][{environment}][{client_ip} {client_browser} {client_os}][{request_method} {request_resource}]:{message}",
"build": false
}
},
"layouts" : {
"{your_layout_name}": {
"pre": ["header","navbar","breadcrumbs"],
"post": ["footer"]
},
"{your_other_layout_name}": {
"pre": ["header", "navbar"],
"post": ["footer"]
}
},
"globals": {
"{name_of_the_constant}": "{value_of_this_constant}",
"{name_of_another_constant}": "{value_of_this_constant}",
}
}
locations
controllers
指向包含您应用程序所有控制器的目录shared
指向包含您应用程序所有共享元素(视图、过滤器)的目录build
指定 layer 将生成用于框架的文件的目录log
指向存储日志文件的目录
environment
routeTemplate
是将添加到所有控制器路由的前缀ApiRouteTemplate
是将添加到所有 ApiController 路由的前缀(例如:对于将 API 错误显示为 JSON 而不是 HTML 视图很有用)log
启用/禁用 LoggerlogTemplate
告诉 Logger 使用每个日志条目的结构build
启用/禁用文件夹(控制器、共享)中的更改扫描以生成新的路由映射(建议:在开发环境中设置为 true,在生产环境中设置为 false)
layouts
- 布局是附加在一起的共享视图,它们仅用于网站,不用于 API,如果您正在构建 API,请将其留空
- 键是您布局的名称,您可以在控制器中使用布局来指定要使用的布局
- 布局由
pre
视图(将在主内容之前渲染)和post
视图(将在主内容之后渲染)组成 - 您可以通过控制器中可用的 viewManager 在运行时更改布局配置
globals
- 您可以在
globals
部分存储任何您想要的内容 - 所有键都将转换为大写常量,例如,
my_key
的值将在整个应用程序中可用为MY_KEY
目录结构
结构组织如下,您有以控制器命名的文件夹,其中包含控制器 php 文件,如果它是网站控制器,您还可以找到包含此控制器使用的视图的视图文件夹。
注意:特定的控制器只能使用它自己的视图文件夹或共享视图,不能使用来自另一个控制器视图文件夹的视图。如果您想为不同的控制器重用视图,请创建一个共享视图!
/mycontrollers
/home
HomeController.php
/views
index.php
contact.php
about.php
/blog
BlogController.php
/views
list-post.php
edit-post.php
/auth
AuthController.php
/views
login.php
signin.php
/api
/user
UserApiController.php
/blog
BlogApiController.php
/shared
/filters
AuthFilter.php
LogFilter.php
GlobalFilter.php
/views
header.php
modal.php
footer.php
/alerts
success.php
warning.php
failure.php
创建一个index.php
这将是您应用程序的主要入口点,每个请求都会触发此脚本,您不应在其中显示任何内容,但您可以在其中添加代码以进行其他原因。
// require autoloader
require_once "./vendor/autoload.php";
// init app with configuration file path
$app = rloris\layer\App::getInstance("./configuration.json");
// execute app
if($code = $app->execute())
{
// request or error handled successfully
rloris\layer\utils\Logger::write("Serving content successfully with status code: $code");
}
else
{
// error could not be handled
rloris\layer\utils\Logger::write("Error occurred");
}
为Apache服务器设置 .htaccess
所有请求都应转发到您的index.php入口文件
<IfModule mod_rewrite.c>
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_URI} !-d
RewriteCond %{REQUEST_URI} !-f
RewriteCond %{REQUEST_URI} !-l
RewriteRule ^(public)($|/) - [L]
RewriteRule ^(.*)$ index.php?url=$1 [L,QSA]
</IfModule>
这会将所有请求重定向到index.php文件或公共文件夹,其中包含您的css、图片、js等
为nginx服务器设置
location -d {}
location -f {}
location -l {}
location ~ ^/(public)($|/) { }
location / {
rewrite ^(.*)$ /index.php?url=$1 break;
}
所有请求都应转发到您的index.php入口文件或公共文件夹,其中包含您的css、js、图片等
设置完成
一旦设置完成,您就可以根据下一章中的说明创建控制器类、视图文件和过滤器类。Layer通过在类和方法上使用注解来自动构建路由映射,无需告诉路由器添加路由。Layer通过使用哈希值检测文件是否已更新,只重建更新的部分。
控制器
您不应在控制器中显示任何内容,但想要显示内容时,应将其传递给视图。为什么?因为Layer处理并发送头信息,在控制器或过滤器中显示内容会破坏此系统,并且您的自定义Layer头信息将不会被发送。
Layer通过在配置文件中指定的控制器文件夹中检测控制器,并且控制器的类名包含{Your_Name}Controller.php
来检测控制器,一旦您的类创建完成,只需扩展它以适应您的特定需求
- 为网站控制器设置BaseController
- 为API控制器设置ApiBaseController
- 为网站错误控制器设置ErrorBaseController
- 为API错误控制器设置ApiErrorBaseController
然后添加一个注解来告诉Layer如何处理此控制器的路由
注解
API控制器的注解
将这些放在ApiBaseController类的顶部 @ApiController
/**
* @ApiController(routeTemplate='users', defaultAction='getUsers')
*/
class UserApiController extends ApiBaseController { ... }
这意味着当您访问/api/users/
时,默认操作是getUsers()
您不需要添加/api
,因为它已经通过读取配置文件中的“apiRouteTemplate”键完成,位于environment/{current_env}/apiRouteTemplate
如果您将配置文件中的此键更改为myApi
,则所有API路由都可通过访问/myApi/{route}
来访问
然后,对于此类中您想要访问的每个公共方法,添加此注解 @ApiAction
// inside UserApiController class
/**
* @ApiAction(routeTemplate='/',methods={'get'})
*/
public function getUsers() { ... }
/**
* @ApiAction(routeTemplate='/',methods={'options'})
*/
public function getOptions() { /* Handle CORS */ }
getUsers()操作将在您使用GET方法访问/api/users/
时被触发
网站控制器的注解
将这些放在BaseController类的顶部 @DefaultController
或 @Controller
。如果它是没有指定路由时要触发的控制器(默认路由),则放在@DefaultController
上
/**
* @DefaultController(routeTemplate='home', layoutName='basic')
*/
class HomepageController extends BaseController { ... }
如果访问/
,我的请求将内部转发到HomepageController
,我也可以访问/home
来访问此控制器,您的项目中应只有一个@DefaultController
,其他应该是@Controller
@Controller
和 @DefaultController
的默认操作是index(),您当然可以通过指定其他一个在这些注解中来自定义它
然后,对于此类中您想要访问的每个公共方法,添加此注解 @Action
// inside HomepageController class
/**
* @Action(methods={"get"})
*/
public function index() { ... }
/**
* @Action(methods={"get"})
*/
public function about_us() { ... }
/**
* @Action(methods={"get"})
*/
public function contact() { ... }
如果您在注解中未指定routeTemplate,则将使用方法名,因此通过访问/home/about_us
或/home/contact
将触发about_us()或contact()操作
用于处理网站错误的错误控制器
将这些放在ErrorBaseController类的顶部 @ErrorController
/**
* @ErrorController(layoutName='basic')
*/
class ErrorsController extends ErrorBaseController { ... }
这告诉层使用此类来处理网站抛出的所有错误
然后在控制器内部,将此注解放在处理特定错误的方法上方
// inside ErrorsController class
/**
* @ErrorAction(errorCodes={"5\d\d"}, viewName='index')
*/
public function serverError() { ... }
/**
* @ErrorAction(errorCodes={"404", "400"}, viewName='index')
*/
public function notFoundError() { ... }
/**
* @ErrorAction(viewName='index')
*/
public function clientError() { ... }
在这种情况下,所有带有HTTP状态码5xx
的错误将触发serverError()
,所有带有HTTP状态码404
和400
的错误将触发notFoundError()
,其余的错误将触发clientError()
API错误控制器用于处理API错误
将这些注解放在ApiErrorBaseController类上方 @ApiErrorController
/**
* @ApiErrorController
*/
class ApiErrorsController extends ApiErrorBaseController { ... }
这告诉层处理所有API错误并将它们转发到该控制器
然后只需通过指定要触发的操作来简单地告诉层如何处理特定错误,将此注解放在控制器内的方法上 @ApiErrorAction
// inside ApiErrorsController class
/**
* @ApiErrorAction(errorCodes={"4\d\d"})
*/
public function clientError() { ... }
/**
* @ApiErrorAction(errorCodes={"5\d\d"})
*/
public function serverError() { ... }
在这种情况下,所有带有HTTP状态码5xx
的错误将触发serverError()
操作,所有带有HTTP状态码4xx
的错误将触发clientError()
操作
路由参数
Layer通过名称处理路由参数,您可以在routeTemplate (控制器和操作)中定义参数,如下所示
/**
* @Controller(routeTemplate='auth/{#identifier}', layoutName='basic', filters={'time'})
*/
class AuthController extends BaseController { ... }
在这里,我在AuthController中声明了一个必须的数字类型参数,这意味着所有操作都需要此参数才能被触发,当然,它们将能够获取它
如果您的参数是必须的并且可以是任何东西,声明它的语法是,{param}
如果您的参数是必须的并且是数字,声明它的语法是,{#param}
如果您的参数不是必须的,只需在末尾添加?
即可,无论是数字还是其他,{param?}
然后要获取此参数,只需将其名称作为方法参数使用即可,如下所示
// inside AuthController class
/**
* @ApiAction(methods={"get"})
*/
public function connect($identifier) { ... }
如您所注意到的,我设置了控制器的routeTemplate,因此我可以在此控制器内的所有方法中使用$identifier
,如果您只想对操作获得相同的结果,可以这样做
/**
* @Controller(routeTemplate='auth', layoutName='basic', filters={'time'})
*/
class AuthController extends BaseController {
/**
* @ApiAction(routeTemplate='{#identifier}', methods={"get"})
*/
public function connect($identifier) { ... }
}
这里的区别在于,只有connect方法将能够使用$identifier
,因为我没有为控制器声明它
过滤器(中间件)
过滤器是在主操作之前和/或之后执行的操作,过滤器是类,它们用于在访问特定资源之前测试用户是否已连接,或者简单地记录某些内容
您可以在控制器或apiController上应用过滤器(这意味着它将应用于控制器内的所有操作)或仅针对指定的一些操作
Layer在配置.json文件中指定的共享文件夹中检测过滤器,并且过滤器的类名包含{Your_Name}Filter.php
,一旦创建类,只需使用BaseFilter
类扩展它即可。
然后,添加此注解: @Filter
eg:
/**
* @Filter
*/
class AuthFilter extends BaseFilter
{
public function in()
{
/* input stuff here */
}
public function out()
{
/* output stuff here */
}
}
您可以在多个操作或控制器上使用过滤器,顺序定义了哪个过滤器将首先被调用,如下所示
- 要将过滤器应用于控制器(因此应用于此控制器中的每个操作)
/**
* @DefaultController(filters={'time', 'log'})
*/
class HomepageController extends BaseController { ... }
在这种情况下,输入时,首先将调用名为time的过滤器,然后调用log,输出时,首先调用log,然后调用time。
- 要将过滤器应用于操作
/**
* @Action(methods={"post"}, filters={"auth"})
*/
public function upload()
{
self::$data = ["content" => "File uploaded with success", "title" => 'Upload file'];
}
如果upload在HomepageController中,那么过滤器将以以下顺序应用,在输入时
time > log > auth
然后调用upload()方法,在输出时
auth > log > time
您可以通过将注解更改为@GlobalFilter
来定义全局过滤器,它们将在每次都应用
/**
* @GlobalFilter
*/
class MyGlobalFilter extends BaseFilter
{
public function in()
{
// CORS allows all origins for api
if(strtoupper(self::$request->getRequestMethod()) === IHttpMethods::OPTIONS) {
self::cors()->allowAnyOrigins()->allowAnyMethods()->allowAnyHeaders();
}
}
public function out()
{
/* output stuff here */
}
}
您可以通过在Controller类和Filter类中使用的filterManager在运行时添加和删除过滤器
self::$filterManager->add("auth");
if(self::$filterManager->isActive("time"))
self::$filterManager->remove("time");
视图
视图是包含主要HTML和一些PHP代码的PHP文件,但你不应该在那里放置业务逻辑。要将内容传递给视图,与发送API数据的方式相同,只需在你的控制器或过滤器中添加self::data[yourkey] = content;
即可,然后像这样访问它:<?= $this->yourkey ?>
。如果你在视图中访问的内容不存在,它将不会显示任何内容,也不会抛出错误。
由于self::$viewManager
可用,你可以通过扩展BaseController
类在运行时添加和删除视图。
PreViews
是在主内容之前显示的共享视图。
PostViews
是在主内容之后显示的共享视图。
ContentView
是在预览之后、在postviews之前显示的主要内容视图。
// getting GET parameter with name ok
$ok = self::$request->getGet('ok');
// adding alert shared view before main content of this action
if($ok === 'true')
{
self::$viewManager->addPreView('alerts/success');
}
else if($ok === 'false')
{
self::$viewManager->addPreView('alerts/failure');
}
else
{
self::$viewManager->addPreView('alerts/alert');
}
工具
请求
你可以在任何控制器或过滤器类中通过输入self::$request
来访问请求。
DOC TODO
响应
你可以在任何控制器或过滤器类中通过输入self::$response
来访问响应。
DOC TODO
文件
文件API允许你轻松处理文件,无论是上传的文件还是其他文件。
DOC TODO
会话
你可以在任何控制器或过滤器类中通过输入self::session()
来访问会话管理器。
DOC TODO
CORS
你可以在任何控制器或过滤器类中通过输入self::cors()
来访问CORS管理器。
DOC TODO
日志记录器
你可以使用日志记录器直接使用配置文件中的特定模板进行写入Logger::write("Hello World")
DOC TODO
接下来计划做什么
- 为API响应添加XML输出支持
- 添加路由参数定制的注解支持,如
@RouteParam(name="id", regex="...")
- 添加WebSocket服务器支持 => (是否需要扩展?)
- 添加LORM(层对象关系映射)支持 => (是否需要扩展?)