rguezque / katya-router
轻量级PHP路由器。
Requires
- php: >=8.1.9
This package is auto-updated.
Last update: 2024-10-01 05:52:22 UTC
README
轻量级PHP路由器
目录表
安装
在项目根目录下的终端
composer require rguezque/katya-router
配置
对于 Apache 服务器,在项目目录中创建并编辑一个 .htaccess
文件,内容如下
<IfModule mod_rewrite.c>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
对于 Nginx,按照以下方式编辑配置文件
server {
location / {
try_files $uri $uri/ /index.php;
}
}
对于使用PHP内置服务器进行测试,在项目目录中,在终端执行
php -S localhost:80
然后在网页浏览器中打开地址 https://:80
自动加载
在终端中,定位到项目目录并执行
composer dump-autoload -o
路由
require __DIR__.'/vendor/autoload.php'; use rguezque\{ HttpStatus, Katya, Request, Response }; use rguezque\Exceptions\{ RouteNotFoundException, UnsupportedRequestMethodException }; $router = new Katya; $router->route(Katya::GET, '/', function(Request $request, Response $response) { $response->send('hola mundo!'); }); try { $router->run(Request::fromGlobals()); } catch(RouteNotFoundException $e) { $message = sprintf('<h1>Not Found</h1><p>%s</p>', $e->getMessage()); (new Response($message, HttpStatus::HTTP_NOT_FOUND))->send(); } catch(UnsupportedRequestMethodException $e) { $message = sprintf('<h1>Not Allowed</h1><p>%s</p>', $e->getMessage()); (new Response($message, HttpStatus::HTTP_METHOD_NOT_ALLOWED))->send(); }
每个路由都通过 Katya::route
方法定义,该方法接收3个参数:请求方法(仅支持 GET
、POST
、PUT
、PATCH
和 DELETE
)、路由和对应路由的控制器。控制器始终接收2个参数:一个 Request
对象(查看 Request)和一个 Response
对象(查看 Response)。第一个包含处理请求所需的方法,第二个包含允许返回响应的方法。
要启动路由器,请调用 Katya::run
方法,并传递一个 Request
对象。
如果路由器位于子目录中,可以在创建路由器实例时在选项数组中指定它。同样,可以定义渲染模板时默认搜索的目录。
$katya = new Katya([ 'basepath' => '/nombre_directorio_base', 'viewspath' => __DIR__.'/templates/' ]);
提示
路由器返回两种可能的异常;当找不到路由时返回 RouteNotFoundException
,当请求方法不受路由器支持时返回 UnsupportedRequestMethodException
。使用 try-catch
来捕获它们,并像示例中那样处理适当的 Response
。
快捷键
快捷方式 Katya::get
、Katya::post
、Katya::put
、Katya::patch
和 Katya::delete
分别用于向路由器添加类型为 GET
、POST
、PUT
、PATCH
和 DELETE
的路由。
$katya = new Katya; $katya->get('/', function(Request $request, Response $response) { $response->send('Hello') }); $katya->post('/', function(Request $request, Response $response) { $data = [ 'name' => 'John', 'lastname' => 'Doe' ]; $response->json($data); });
控制器
控制器可以是:匿名函数、静态方法或对象方法。
// Usando una función anónima $katya->get('/user', function(Request $request, Response $response) { //... }); // Usando un método estático $katya->get('/user', ['App\Controller\Greeting', 'showProfile']); // o bien use App\Controller\User; $katya->get('/user', [User::class, 'showProfile']); $katya->get('/user/permissions', [User::class, 'showPermissions']); // Usando un método de un objeto $user = new App\Controller\User(); $katya->get('/user', [$user, 'showProfile']);
路由分组
要创建具有相同前缀的路由分组,请使用 Katya::group
;它接受2个参数:路由前缀和一个接收包含组路由定义的 Group
对象的匿名函数。
// Se generan las rutas "/foo/bar" y "/foo/baz" $katya->group('/foo', function(Group $group) { $group->get('/bar', function(Request $request, Response $response) { $response->send(' Hello foobar'); }); $group->get('/baz', function(Request $request, Response $response) { $response->render('welcome.php') }); });
通配符
通配符是路由中定义的参数。路由器根据请求查找匹配项,并将它们作为参数发送到通过 Request
对象传递的路由控制器,这些参数可以通过 Request::getParams
方法检索,该方法返回一个 Parameters
对象,其中每个键都与通配符的名称相对应。
$katya->get('/hola/{nombre}', function(Request $request, Response $response) { $params = $request->getParams(); $response->send(sprintf('Hola %s', $params->get('nombre'))); });
如果将 通配符 定义为正则表达式,则可以使用 Request::getMatches
方法进行检索,该方法返回一个包含找到的匹配项值的 线性数组。
$katya->get('/hola/(\w+)/(\w+)', function(Request $request, Response $response) { $params = $request->getMatches(); list($nombre, $apellido) = $params; $response->send(sprintf('Hola %s %s', $nombre, $apellido)); });
重要
请避免在同一个路由定义中混合命名参数和正则表达式,因为您将无法按名称检索被定义为 正则表达式 的参数。无论如何,如果发生这种情况,请使用包含在路由定义中按定义顺序的所有参数的 Request::getMatches
。
视图
视图是通过路由器返回和渲染包含 HTML 内容的 Response
对象到浏览器的方式。所需配置只有一个,即定义模板所在目录。
use rguezque\View; $view = new View( __DIR__.'/mis_plantillas', // Directorio donde se alojan los templates );
可以使用 View::setViewsPath
方法覆盖 View
的初始配置。
$view->setPath(__DIR__.'/templates');
注意
如果在路由器构造函数的初始配置中已定义了模板目录,则不需要在 View
类构造函数中指定它,尽管如果在此处定义了目录,则它将优先于初始配置。
设置模板
定义主模板的方法是 View::setTemplate
,它可以接受一个或两个参数;第一个参数是模板文件的名称,第二个是一个关联数组,包含传递给模板的参数。
// app/Http/FooController.php function __construct(View $view) { $this->view = $view; } public function homeAction(Request $request, Response $response): Response { $result = $this->view->template('home.php', ['message' => 'Hola mundo!'])->render(); return $response->withContent($result); }
添加参数
将参数发送到视图的另一种方法是使用 View::addArgument
和 View::addArguments
方法。第一个方法接受两个参数(名称和值),第二个方法接受一个关联数组。这些参数将在调用 View::render
方法时自动包含,因此必须在渲染之前声明(参见 渲染)。
// Se agrega un solo argumento $view->addArgument('message', 'Hello weeerld!'); // Se agregan varios argumentos a la vez $view->addArguments([ 'id' => 1, 'name' => 'Banana', 'color' => 'yellow' ]);
扩展模板
要扩展模板,请使用 View::extendWith
方法,该方法接受三个参数;要扩展的主模板名称,一个唯一别名,用于在主模板中包含它,以及可选地一个将发送到当前正在扩展主模板的模板的参数数组。
$data = [ 'home': '/', 'about': '/about-us', 'contact': '/contact-us' ]; // Se guarda el template menu.php con el alias 'menu_lateral' y se le envian parámetros en la variable $data $view->template('index.php', ['title' => 'Ejemplo de vistas']); $view->extendWith('menu.php', 'menu_lateral', $data); $view->render();
接收在 $data
中发送的参数(如上代码块示例所示)
//menu.php <nav> <ul> <li><a href="<?= $home ?>">Home</a></li> <li><a href="<?= $about ?>">About</a></li> <li><a href="<?= $contact ?>">Contact</a></li> </ul> </nav>
在屏幕上打印先前使用别名 'menu_lateral'
保存的 menu.php 的内容。
// index.php <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= $title ?></title> </head> <body> <?php echo $menu_lateral ?> </body> </html>
渲染视图
View::render
方法始终在最后调用,并返回当前 buffer 中的内容,以便在一个变量中检索并发送到 Response
。
注意
简单渲染模板的快捷方式是通过 Response::render
方法(参见 Response)。
请求
Request
类的方法。
fromGlobals()
:创建一个包含 PHP 全局变量的Request
对象。getQuery()
:返回$_GET
参数数组。getBody()
:返回$_POST
参数数组。getPhpInputStream(int $option = 0)
:返回未处理的php://input
流。如果以 JSON 格式接收请求,则发送一个整型参数(JSON_DECODE = 2
)并调用getPhpInputStream(Request::JSON_DECODE)
;如果是字符串(PARSED_STR = 1
),则调用getPhpInputStream(Request::PARSED_STR)
。getServer()
:返回$_SERVER
参数数组。getCookies()
:返回$_COOKIE
参数数组。getFiles()
:返回$_FILES
参数数组。getParams()
:返回请求路由的命名参数数组。getParam(string $name, $default = null)
:返回请求路由的命名参数。getMatches()
:返回定义在路由中的正则表达式匹配的数组。setQuery(array $query)
:将值分配给$_GET
。setBody(array $body)
:将值分配给$_POST
。setServer(array $server)
:将值分配给$_SERVER
。setCookies(array $cookies)
:将值分配给$_COOKIE
。setFiles(array $files)
: 为$_FILES
分配值。setParams(array $params)
: 为命名参数数组分配值。setParam(string $name, $value)
: 向命名参数数组添加一个值。unsetParam(string $name)
: 通过名称删除一个参数。setMatches(array $matches)
: 向正则表达式匹配数组添加值。buildQuery(string $uri, array $params)
: 在URI中生成并返回一个GET
请求字符串。
客户端请求
ClientRequest
类表示客户端的HTTP请求。
use Forge\Route\ClientRequest; // Si se omite el segundo parámetro se asume que será una petición GET $client_request = new ClientRequest('https://jsonplaceholder.typicode.com/posts'); // Se envía la petición y se almacena la respuesta $client_request->send(); // Se recupera el valor almacenado $result = $client_request->toArray();
可用方法
withRequestMethod(string $method)
: 指定要进行的请求类型(GET
、POST
、PUT
、DELETE
)。withHeader(string $key, string $value)
: 向请求添加一个标题。withHeaders(array $headers)
: 向请求添加多个标题,接收一个关联数组作为参数,其中每个键是一个标题,后跟其内容。withPostFields($data, bool $encode = false)
: 通过一个关联数组的数据添加参数到请求中。第二个参数定义是否编码为JSON格式。withBasicAuth(string $username, string $password)
: 添加一个基于用户名和密码的简单Authorization
标题。withTokenAuth(string $token)
: 添加一个基于JWT的Authorization
标题。send()
: 发送请求并将响应存储。如果出错,将抛出CurlException
。getContent()
: 获取响应的值。toArray()
: 以JSON格式获取响应的值。getInfo()
: 返回一个关联数组,包含有关已发送请求的信息。如果在与ClientRequest::send()
之前调用,将返回null
。
响应
Response
类的成员方法。
clear()
: 清除Response
的值。status(int $code)
: 分配HTTP状态码。header(string $name, string $content)
: 向Response
添加一个标题。headers(array $headers)
: 向Response
添加多个标题。write($content)
: 向Response
的正文添加内容。send($data)
: 发送Response
。json($data, bool $encode = true)
: 返回包含JSON格式内容的Response
。render(string $template, array $arguments = [])
: 以模板渲染的形式返回Response
。它将在路由器构造函数中定义的配置目录中查找模板。如果没有定义默认目录,则必须指定模板的完整路径。redirect(string $uri)
: 返回一个作为重定向的Response
。
会话
Session
类用于创建会话并管理存储在$_SESSION
中的变量。通过使用new Session('sesion_name')
或直接使用别名Session::select('sesion_name')
来分配一个名称来初始化或选择一个变量集合。可用方法有:
start()
: 启动会话。started()
: 如果会话是活动的,则返回true
。set(string $key, $value)
: 创建或覆盖一个会话变量。get(string $key, $default = null)
: 返回一个会话变量,如果不存在则返回第二个参数中指定的默认值。getNamespace()
: 返回当前会话命名空间的名字。all()
: 返回一个包含当前namespace中所有会话变量的数组。has(string $key)
: 如果存在一个会话变量,则返回true
。valid(string $key)
: 如果一个会话变量不是null
且不为空,则返回true
。remove(string $key)
: 删除一个会话变量。clear()
:删除所有会话变量。destroy()
;销毁当前会话以及相关cookie和会话变量。
服务
Services
类用于注册在整个项目中使用的服务。通过Services::register
方法添加服务,该方法接收两个参数,一个名称和一个匿名函数。要删除服务,使用Services::unregister
,它接收要删除的服务名称(或服务,用逗号分隔)。
要将其分配给路由器,请通过Katya::setServices
方法发送Services
对象,从此时起,每个控制器将接收一个Services
实例作为第三个参数。调用服务就像调用类的一个方法一样,或者在对象上下文中作为一个属性。
可选地,可以使用Route::useServices
选择在特定路由或路由组中使用哪些服务,该方法接收已注册服务的名称,名称之间用逗号分隔。
要检查服务是否存在,使用Services::has
方法(发送服务名称作为参数)和Services::names
返回一个包含所有可用服务名称的数组。
require __DIR__.'/vendor/autoload.php'; use rguezque\{Group, Katya, Request, Response, Services}; $router = new Katya; $services = new Services; $services->register('pi', function() { return 3.141592654; }); $services->register('is_pair', function(int $number) { return $number % 2 == 0; }); $router->setServices($services); $router->get('/', function(Request $request, Response $response, Services $service) { $pi = $service->pi(); // o bien en contexto de objeto: $service->pi $response->clear()->send($pi); })->useServices('pi'); // Solamente recibirá el servicio 'pi'
变量
使用Katya::setVariables
在应用程序中分配全局变量,它接收一个Variables
对象作为参数。
require __DIR__.'/vendor/autoload.php'; use rguezque\{Katya, Request, Response, Variables}; $router = new Katya; $vars = new Variables; $vars->setVar('pi', 3.141592654); $router->setVariables($vars); $router->get('/', function(Request $request, Response $response, Variables $vars) { $response->send($vars->getVar('pi')); });
使用Variables::setVar
创建一个变量,接收变量名称和其值作为参数。
$vars->setVar('pi', 3.141592654);
使用Variables::getVar
方法获取变量,接收变量名称和一个默认值,如果所调用的变量不存在;最后一个参数是可选的,如果没有声明,将返回默认值null
。
$vars->getVar('pi'); // Devuelve la variable pi (si no existe devuelve null) $vars->getVar('pi', 3.14) // Devuelve la variable pi (si no existe devuelve por default el valor 3.14)
要检查变量是否存在,使用Variables::hasVar
方法,如果变量存在则返回true
,否则返回false
。
$vars->hasVar('pi') // Para este ejemplo devolvería TRUE
注意
所有变量名称都规范化为小写,并且总是作为每个控制器的最后一个参数发送,只有当它们通过Katya::setVariables
定义和分配时。
数据库连接
DbConnection
类通过PDO
驱动程序或mysqli
类提供创建与MySQL的singleton连接的方法。静态方法DbConnection::getConnection
接收连接参数并返回一个包含已创建连接的对象,取决于driver
参数,其中定义是否默认使用PDO
或mysqli
。
use rguezque\DbConnection; $db = DbConnection::getConnection([ // 'driver' => 'mysqli', 'driver' => 'pdomysql', 'host' => 'localhost', 'port' => 3306, 'user' => 'root', 'pass' => 'mypassword', 'dbname' => 'mydatabase' 'charset' => 'utf8' ]);
使用URL连接
另一种选择是使用数据库URL作为连接参数,通过静态方法DbConnection::dsnParser
;它接收一个URL并将其处理为以这种方式发送到DbConnection::getConnection
use rguezque\DbConnection; // Con mysqli // 'mysqli://root:mypassword@127.0.0.1/mydatabase?charset=utf8' // Con PDO $connection_params = DbConnection::dsnParser('pdomysql://root:mypassword@127.0.0.1/mydatabase?charset=utf8'); $db = DbConnection::getConnection($connection_params);
自动连接
静态方法DbConnection::autoConnect
自动连接到MySQL,并自动使用在.env
文件中定义的参数。
use rguezque\DbConnection; $db = DbConnection::autoConnect();
.env
文件可能看起来像这样
DB_DRIVER="mysqli"
DB_NAME="mydatabase"
DB_HOST="127.0.0.1"
DB_PORT=3306
DB_USER="root"
DB_PASS="mypassword"
DB_CHARSET="utf8"
注意
应使用某些库来处理在.env
中存储的变量,并将它们加载到$_ENV
变量中。最常用的是vlucas/phpdotenv
。
中间件
Route::before
中间件在执行路由控制器之前执行一个动作。
Route::before
接收一个callable
对象(函数、对象方法或静态方法),在其中定义要执行的动作,该对象本身接收与控制器相同的参数:Request
和Response
实例以及定义了服务时的Services
实例。如果返回一个值,则通过Request
对象传递给控制器,并通过Request::getParam
或返回的数组从Request::getParams
中检索键@middleware_data
。
路由和路由组都可以有中间件。如果在一个组中定义,则所有路由都将继承相同的预处理动作,但如果在一个单独的路由中定义,则该路由将优先于组的中继件。
require __DIR__.'/vendor/autoload.php'; use rguezque\{Group, Katya, Request, Response, Session}; $router = new Katya; $router->get('/', function(Request $request, Response $response) { $username = $request->getParam('@data'); $response->clear()->send(sprintf('The actual user is: %s'), $username); })->before(function(Request $request, Response $response) { $session = Session::select('mi_sesion'); if(!$session->has('logged')) { $response->redirect('/login'); } return $session->get('username'); }); $router->group('/admin', function(Group $group) { $group->get('/clients', function(Request $request, Response $response) { // Do something }); $group->get('/customers', function(Request $request, Response $response) { // Do something }); })->before(function(Request $request, Response $response) { $session = Session::select('mi_sesion'); if(!$session->has('logged') || !$session->has('logged_as_admin')) { $response->redirect('/login'); } });
CORS
方法 Katya::cors
允许定义一个允许进行资源限制请求的外部域名数组,这被广泛称为 CORS (跨源资源共享)。同样,还可以指定允许的请求方法,通过将它们作为一个数组作为第二个参数传递。
require __DIR__.'/vendor/autoload.php'; use rguezque\Katya; $router = new Katya; // Ejemplo $router->cors( [ '(http(s)://)?(www\.)?localhost:3000' ], ['GET', 'POST'] // En este ejemplo solo se permiten estos métodos );
还可以从路由器构造函数中指定完整的CORS配置。
require __DIR__.'/vendor/autoload.php'; use rguezque\Katya; $router = new Katya([ 'cors' => [ 'origins' => [ '(http(s)://)?(www\.)?fakesite.com' ], 'methods' => ['GET', 'POST'] ] ]);
注意
如果在构造函数中定义了CORS配置,则无需再次使用 Katya::cors
进行配置,因为后者将不会产生效果。