juneszh/alight

Alight 是一个轻量级的 PHP 框架。轻松快速构建高性能的 RESTful 网络应用程序。

v2.1.1 2024-07-12 09:13 UTC

This package is auto-updated.

Last update: 2024-09-29 08:37:02 UTC


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!

其他

还有一些有用的助手函数放在不同的命名空间中。请点击文件查看详细信息

鸣谢

许可