juneszh / alight
Alight 是一个轻量级的 PHP 框架。轻松快速构建高性能的 RESTful 网络应用程序。
Requires
- php: >=7.4
- catfan/medoo: ^2.1
- filp/whoops: ^2.15
- monolog/monolog: >=2.9
- nikic/fast-route: ^1.3
- psr/simple-cache: >=1.0
- symfony/cache: >=5.4
README
Alight 是一个轻量级的 PHP 框架。轻松快速构建高性能的 RESTful 网络应用程序。内置路由、数据库、缓存、错误处理、日志和作业调度库。专注于创建网络应用程序的核心流程解决方案。保持简单且可扩展。
Alight 家族
要求
PHP 7.4+
入门
安装
步骤 1:安装 Composer
没有 Composer?请先安装 Composer。
步骤 2:创建项目
使用 create-project 创建模板
$ composer create-project juneszh/alight-project {PROJECT_DIRECTORY}
项目模板包含常见的文件夹结构,适合 MVC 模式,请参考:Alight-Project。
可以通过修改配置轻松自定义文件夹。但以下教程基于模板配置。
步骤 3:配置 Web 服务器
Nginx 示例(Nginx 1.17.10,PHP 7.4.3,Ubuntu 20.04.3)
server { listen 80; listen [::]:80; root /var/www/{PROJECT_DIRECTORY}/public; index index.php; server_name {YOUR_DOMAIN}; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; } }
配置
Alight 框架的所有配置选项都将从文件 'config/app.php' 中导入,您需要自己创建该文件。例如
文件:config/app.php
<?php return [ 'app' => [ 'debug' => false, 'timezone' => 'Europe/Kiev', 'storagePath' => 'storage', 'domainLevel' => 2, 'corsDomain' => null, 'corsHeaders' => null, 'corsMethods' => null, 'cacheAdapter' => null, 'errorHandler' => null, 'errorPageHandler' => null, ], 'route' => 'config/route/web.php', 'database' => [ 'type' => 'mysql', 'host' => '127.0.0.1', 'database' => 'alight', 'username' => 'root', 'password' => '', ], 'cache' => [ 'type' => 'file', ], 'job' => 'config/job.php', ];
获取一些配置项
<?php Alight\Config::get('app'); Alight\Config::get('app', 'storagePath');
可用配置
详细信息请参阅 Config.php。
路由
在学习路由规则之前,您需要先创建一个存储路由规则的 PHP 文件。因为路由缓存的更新与否,取决于路由文件的修改时间。例如
文件:config/route/web.php
Alight\Route::get('/', 'Controller::index');
文件:config/app.php
<?php return [ 'route' => 'config/route/web.php' // Also supports multiple files // 'route' => ['config/route/web.php', config/route/api.php'] ];
顺便说一下,路由配置支持为 子域名 导入指定的文件
<?php return [ 'route' => [ //Import on any request '*' => 'config/route/web.php', //Import when requesting admin.yourdomain.com 'admin' => 'config/route/admin.php', //Import multiple files when requesting api.yourdomain.com 'api' => ['config/route/api.php', 'config/route/api_mobile.php'], ] ];
基本用法
Alight\Route::get($pattern, $handler); // Example Alight\Route::get('/', 'Controller::index'); Alight\Route::get('/', ['Controller', 'index']); // Or try this to easy trigger hints from IDE Alight\Route::get('/', [Controller::class, 'index']); // With default args Alight\Route::get('post/list[/{page}]', [Controller::class, 'list'], ['page' => 1]); // Common HTTP request methods Alight\Route::options('/', 'handler'); Alight\Route::head('/', 'handler'); Alight\Route::post('/', 'handler'); Alight\Route::delete('/', 'handler'); Alight\Route::put('/', 'handler'); Alight\Route::patch('/', 'handler'); // Map for Custom methods Alight\Route::map(['GET', 'POST'], 'test', 'handler'); // Any for all common methods Alight\Route::any('test', 'handler');
正则表达式
// Matches /user/42, but not /user/xyz Alight\Route::get('user/{id:\d+}', 'handler'); // Matches /user/foobar, but not /user/foo/bar Alight\Route::get('user/{name}', 'handler'); // Matches /user/foo/bar as well, using wildcards Alight\Route::get('user/{name:.+}', 'handler'); // The /{name} suffix is optional Alight\Route::get('user[/{name}]', 'handler'); // Root wildcards for single page app Alight\Route::get('/{path:.*}', 'handler');
nikic/fast-route 处理路由路径中的所有正则表达式。有关详细信息,请参阅 FastRoute 使用。
选项
分组
Alight\Route::group('admin'); // Matches /admin/role/list Alight\Route::get('role/list', 'handler'); // Matches /admin/role/info Alight\Route::get('role/info', 'handler'); // Override the group Alight\Route::group('api'); // Matches /api/news/list Alight\Route::get('news/list', 'handler');
自定义 'any'
您可以为 Alight\Route::any()
中的方法进行自定义。
Alight\Route::setAnyMethods(['GET', 'POST']); Alight\Route::any('only/get/and/post', 'handler');
处理程序前
如果您想在路由处理程序之前运行一些常见代码。
// For example log every hit request Alight\Route::beforeHandler([svc\Request::class, 'log']); Alight\Route::get('test', 'handler'); Alight\Route::post('test', 'handler');
禁用路由缓存
不推荐,但如果您的代码需要
// Effective in the current route file Alight\Route::disableCache();
生命周期
所有路由选项仅在当前文件中生效,并在导入下一个文件之前由 Alight\Route::init()
自动重置。例如
文件:config/admin.php
Alight\Route::group('admin'); Alight\Route::setAnyMethods(['GET', 'POST']); // Matches '/admin/login' by methods 'GET', 'POST' Alight\Route::any('login', 'handler');
文件:config/web.php
// Matches '/login' by methods 'GET', 'POST', 'PUT', 'DELETE', etc Alight\Route::any('login', 'handler');
工具
Cache-Control 标头
发送 Cache-Control 标头以控制浏览器和共享缓存(CDN)中的缓存,以便优化对未修改数据的访问速度。
// Cache one day Alight\Route::get('about/us', 'handler')->cache(86400); // Or force disable cache Alight\Route::put('user/info', 'handler')->cache(0);
处理用户授权
我们提供了一个简单的授权处理程序来管理用户登录状态。
// Define a global authorization verification handler Alight\Route::authHandler([\svc\Auth::class, 'verify']); // Enable verification in routes Alight\Route::get('user/info', 'handler')->auth(); Alight\Route::get('user/password', 'handler')->auth(); // No verification by default Alight\Route::get('about/us', 'handler'); // In general, routing with authorization will not use browser cache // So auth() has built-in cache(0) to force disable cache // Please add cache(n) after auth() to override the configuration if you need Alight\Route::get('user/rank/list', 'handler')->auth()->cache(3600);
文件:app/service/Auth.php
namespace svc; class Auth { public static function verify() { // Some codes about get user session from cookie or anywhere // Returns the user id if authorization is valid // Otherwise returns 0 or something else for failure // Then use Router::getAuthId() in the route handler to get this id again return $userId; } }
请求冷却
很多时候用户提交的数据需要时间处理,我们不希望在处理之前收到相同的数据。因此,我们需要设置请求冷却时间。当用户在冷却时间内再次请求时,将收到 429 错误。
// Cooldown only takes effect when authorized Alight\Route::put('user/info', 'handler')->auth()->cd(2); Alight\Route::post('user/status', 'handler')->auth()->cd(2);
跨源资源共享(CORS)
当您的API需要由第三方网站(或您的项目有多个域名)用于Ajax请求时,您需要发送一组CORS头。具体原因请参阅: Mozilla文档。
// Domains in config will receive the common cors header Alight\Route::put('share/config', 'handler')->cors(); // The specified domain will receive the common cors header Alight\Route::put('share/specified', 'handler')->cors('abc.com'); // The specified domain will receive the specified cors header Alight\Route::put('share/specified2', 'handler')->cors('abc.com', 'Authorization', ['GET', 'POST']); // All domains will receive a 'Access-Control-Allow-Origin: *' header Alight\Route::put('share/all/http', 'handler')->cors('*'); // All domains will receive a 'Access-Control-Allow-Origin: [From Origin]' header Alight\Route::put('share/all/https', 'handler')->cors('origin');
如果您的网站正在使用CDN,请谨慎使用此实用程序。以避免在CDN缓存头部后请求失败。
数据库
Alight将'database'配置直接传递给catfan/medoo
。有关特定配置选项,请参阅Medoo入门。例如
文件:config/app.php
<?php return [ 'database' => [ 'type' => 'mysql', 'host' => '127.0.0.1', 'database' => 'alight', 'username' => 'root', 'password' => '', ], // Multiple databases (The first database is default) // 'database' => [ // 'main' => [ // 'type' => 'mysql', // 'host' => '127.0.0.1', // 'database' => 'alight', // 'username' => 'root', // 'password' => '', // ], // 'remote' => [ // 'type' => 'mysql', // 'host' => '1.1.1.1', // 'database' => 'alight', // 'username' => 'root', // 'password' => '', // ], // ] ];
基本用法
Alight\Database::init()
是new Medoo\Medoo()
的静态和单例实现,因此它继承了Medoo()
的所有功能。单例使得每个请求仅连接一次数据库并重用它,从而有效地减少了数据库连接数。
// Initializes the default database $db = \Alight\Database::init(); // Initializes others database with key $db2 = \Alight\Database::init('remote'); $userList = $db->select('user', '*', ['role' => 1]); $userInfo = $db->get('user', '*', ['id' => 1]); $db->insert('user', ['name' => 'anonymous', 'role' => 2]); $id = $db->id(); $result = $db->update('user', ['name' => 'alight'], ['id' => $id]); $result->rowCount();
有关使用说明,请参阅Medoo文档。
缓存
Alight通过symfony/cache
支持多个缓存驱动程序和多个缓存接口。配置选项'dsn'和'options'将传递给缓存适配器,更多详情请参阅可用缓存适配器。例如
文件:config/app.php
<?php return [ 'cache' => [ 'type' => 'file', ], // Multiple cache (The first cache is the default) // 'cache' => [ // 'file' => [ // 'type' => 'file', // ], // 'memcached' => [ // 'type' => 'memcached', // 'dsn' => 'memcached://', // 'options' => [], // ], // 'redis' => [ // 'type' => 'redis', // 'dsn' => 'redis://', // 'options' => [], // ], // ] ];
基本用法(PSR-16)
与数据库类似,Alight\Cache::init()
是缓存客户端的静态和单例实现,以提高并发请求性能。
// Initializes the default cache $cache = \Alight\Cache::init(); // Initializes others cache with key $cache2 = \Alight\Cache::init('redis'); // Use SimpleCache(PSR-16) interface if (!$cache->has('test')){ $cache->set('test', 'hello world!', 3600); } $cacheData = $cache->get('test'); $cache->delete('test');
PSR-6接口
$cache6 = \Alight\Cache::psr6('memcached'); $cacheItem = $cache6->getItem('test'); if (!$cacheItem->isHit()){ $cacheItem->expiresAfter(3600); $cacheItem->set('hello world!'); // Bind to a tag $cacheItem->tag('alight'); } $cacheData = $cacheItem->get(); $cache6->deleteItem('test'); // Delete all cached items in the same tag $cache6->invalidateTags('alight') // Or symfony/cache adapter style $cacheData = $cache6->get('test', function ($item){ $item->expiresAfter(3600); return 'hello world!'; }); $cache6->delete('test');
原生接口
还支持memcached或redis原生接口,用于使用高级缓存
$memcached = \Alight\Cache::memcached('memcached'); $memcached->increment('increment'); $redis = \Alight\Cache::redis('redis'); $redis->lPush('list', 'first');
更多适配器
symfony/cache
支持超过10个适配器,但我们仅内置了3个常用,例如文件系统、memcached、redis。如果您需要更多适配器,可以扩展它。例如
文件:config/app.php
<?php return [ 'app' => [ 'cacheAdapter' => [svc\Cache::class, 'adapter'], ], 'cache' => [ // ... 'apcu' => [ 'type' => 'apcu' ], 'array' => [ 'type' => 'array', 'defaultLifetime' => 3600 ] ] ];
文件:app/service/Cache.php
namespace svc; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\NullAdapter; class Cache { public static function adapter(array $config) { switch ($config['type']) { case 'apcu': return new ApcuAdapter(); break; case 'array': return new ArrayAdapter($config['defaultLifetime']); default: return new NullAdapter(); break; } } }
有关更多信息,请参阅Symfony缓存组件。
错误处理
Alight通过Alight\App::start()
捕获所有错误。当在应用程序配置中打开'debug'时,错误将以漂亮的HTML(由filp/whoops
生成)或JSON格式输出。
文件:config/app.php
<?php return [ 'app' => [ 'debug' => true, ] ];
自定义处理器
在生产环境中关闭'debug'时,Alight仅将错误记录到文件并输出HTTP状态。您可以通过应用程序配置覆盖这些默认行为。例如
文件:config/app.php
<?php return [ 'app' => [ 'errorHandler' => [svc\Error::class, 'catch'], 'errorPageHandler' => [svc\Error::class, 'page'], ] ];
文件:app/service/Error.php
namespace svc; class Error { public static function catch(Throwable $exception) { // Some code like sending an email or using Sentry or something } public static function page(int $status) { switch ($status) { case 400: // Page code... break; case 401: // Page code... break; case 403: // Page code... break; case 404: // Page code... break; case 500: // Page code... break; default: // Page code... break; } } }
作业调度
如果您需要定期在后台运行PHP脚本。
步骤1:设置CRON
$ sudo contab -e
将以下内容添加到最后一行
* * * * * sudo -u www-data /usr/bin/php /var/www/{PROJECT_DIRECTORY}/app/scheduler.php >> /dev/null 2>&1
步骤2:创建任务
文件:config/job.php
Alight\Job::call('handler')->minutely(); Alight\Job::call('handler')->hourly(); Alight\Job::call('handler')->daily(); Alight\Job::call('handler')->weekly(); Alight\Job::call('handler')->monthly(); Alight\Job::call('handler')->yearly(); Alight\Job::call('handler')->everyMinutes(5); Alight\Job::call('handler')->everyHours(2); Alight\Job::call('handler')->date('2022-08-02 22:00');
提示
每个处理器一次只运行一个进程,进程的默认最大运行时间为1小时。如果您的处理器需要更长的运行时间,请使用timeLimit()。
Alight\Job::call('handler')->hourly()->timeLimit(7200);// 7200 seconds
助手
项目根路径
Alight提供Alight\App::root()
以标准化项目中的文件路径格式。
// Suppose the absolute path of the project is /var/www/my_project/ \Alight\App::root('public/favicon.ico'); // /var/www/my_project/public/favicon.ico // Of course, you can also use absolute path files with the first character '/' \Alight\App::root('/var/data/config/web.php');
配置中的所有文件路径都基于Alight\App::root()
。例如
Alight\App::start([ 'route' => 'config/route/web.php', // /var/www/my_project/config/route/web.php 'job' => 'config/job.php' // /var/www/my_project/config/job.php ]);
API响应
Alight提供Alight\Response::api()
以标准化API响应格式。
HTTP 200 OK { "error": 0, // API error code "message": "OK", // API status description "data": {} // Object data }
状态定义
例如
\Alight\Response::api(0, null, ['name' => 'alight']); // Response: // HTTP 200 OK // // { // "error": 0, // "message": "OK", // "data": { // "name": "alight" // } // } \Alight\Response::api(1001, 'Invalid request parameter.'); // Response: // HTTP 200 OK // // { // "error": 1001, // "message": "Invalid request parameter.", // "data": {} // } \Alight\Response::api(500, 'Unable to connect database.'); // Response: // HTTP 500 Internal Server Error // // { // "error": 500, // "message": "Unable to connect database.", // "data": {} // }
视图
Alight提供Alight\Response::render()
以显示视图模板,通过传递模板文件路径和可选的模板数据调用render方法
文件:app/controller/Pages.php
namespace ctr; class Pages { public static function index() { \Alight\Response::render('hello.php', ['name' => 'Ben']); } }
文件:app/view/hello.php
<h1>Hello, <?= $name ?>!</h1>
文件:config/route/web.php
Alight\Route::get('/', [ctr\Pages::class, 'index']);
项目的首页输出将如下
Hello, Ben!
其他
还有一些有用的助手函数放在不同的命名空间中。请点击文件查看详细信息
鸣谢
- Composer需求
- 特别感谢