Learn

了解 Flight

Flight 是一个快速、简单、可扩展的 PHP 框架。它非常多才多艺,可用于构建各种类型的 Web 应用程序。它的设计目标是简单易懂,编写方式简单易用。

重要的框架概念

为什么选择框架?

这是一篇简短的文章,介绍了为什么应该使用框架。在开始使用框架之前了解使用框架的好处是个不错的主意。

另外,@lubiana创建了一份绝佳的教程。虽然该教程没有详细介绍 Flight,但这份指南将帮助您了解围绕框架的一些主要概念以及使用框架的好处。您可以在这里找到该教程。

Flight 与其他框架的比较

如果您正在从其他框架(如 Laravel、Slim、Fat-Free 或 Symfony)迁移到 Flight,本页面将帮助您了解两者之间的区别。

核心主题

自动加载

了解如何在应用程序中自动加载您自己的类。

路由

了解如何管理 Web 应用程序的路由。这还包括分组路由、路由参数和中间件。

中间件

了解如何使用中间件来过滤应用程序中的请求和响应。

请求

了解如何处理应用程序中的请求和响应。

响应

了解如何向用户发送响应。

HTML 模板

了解如何使用内置视图引擎来呈现您的 HTML 模板。

安全

学习如何保护应用程序免受常见安全威胁。

配置

学习如何为应用程序配置框架。

扩展 Flight

学习如何通过添加自己的方法和类来扩展框架。

事件和过滤

学习如何使用事件系统向您的方法和内部框架方法添加钩子。

依赖注入容器

学习如何使用依赖注入容器(DIC)管理应用程序的依赖关系。

框架 API

了解框架的核心方法。

迁移到 v3

向后兼容性在很大程度上得到了保持,但从 v2 迁移到 v3 时应该注意一些变化。

故障排除

在使用 Flight 时可能会遇到一些常见问题。此页面将帮助您解决这些问题。

Learn/stopping

停止

您可以通过调用 halt 方法在任何时候停止框架:

Flight::halt();

您还可以指定一个可选的 HTTP 状态码和消息:

Flight::halt(200, '马上回来...');

调用 halt 将丢弃直到该点的任何响应内容。如果您想要停止框架并输出当前响应,请使用 stop 方法:

Flight::stop();

Learn/errorhandling

错误处理

错误和异常

所有错误和异常都会被 Flight 捕获并传递给error方法。 默认行为是发送一个通用的HTTP 500 内部服务器错误响应,带有一些错误信息。

您可以根据自己的需求覆盖此行为:

Flight::map('error', function (Throwable $error) {
  // 处理错误
  echo $error->getTraceAsString();
});

默认情况下,错误不会记录到 web 服务器。您可以通过更改配置来启用此功能:

Flight::set('flight.log_errors', true);

未找到

当 URL 找不到时,Flight 调用notFound方法。默认行为是发送一个HTTP 404 未找到响应,带有一个简单消息。

您可以根据自己的需求覆盖此行为:

Flight::map('notFound', function () {
  // 处理未找到
});

Learn/flight_vs_laravel

Flight vs Laravel

什么是 Laravel?

Laravel 是一个全功能的框架,拥有一切必需的功能,以及一个令人惊叹的开发者专注生态系统, 但在性能和复杂性方面有一定代价。 Laravel 的目标是使开发者能够拥有最高水平的生产力,并使常见任务变得简单。 对于那些希望构建一个全功能企业 Web 应用程序的开发者来说,Laravel 是一个很好的选择。 但在性能和复杂性方面存在一些折衷。学习 Laravel 的基础知识可能很容易,但要熟练使用该框架可能需要一些时间。

开发者也经常感觉到,有许多 Laravel 模块,实际上可以通过使用另一个库或编写自己的代码来解决问题。

与 Flight 相比的优点

与 Flight 相比的缺点

Learn/migrating_to_v3

升级到 v3

在很大程度上保持了向后兼容性,但在从 v2 迁移到 v3 时有一些更改是您应该注意的。

输出缓冲行为 (3.5.0)

输出缓冲 是指 PHP 脚本生成的输出存储在一个缓冲区(PHP 内部)中,然后再发送给客户端。这允许您在发送给客户端之前修改输出。

在 MVC 应用程序中,控制器是“管理者”,它管理视图的操作。在控制器之外生成输出(或在 Flight 的情况下有时是匿名函数)会破坏 MVC 模式。这种更改是为了更符合 MVC 模式,并使框架更加可预测和易于使用。

在 v2 中,输出缓冲处理方式是不一致关闭自己的输出缓冲区,这使得单元测试流处理更加困难。对大多数用户来说,这种更改实际上可能不会影响您。但是,如果您在可调用函数和控制器之外输出内容(例如在钩子中),您可能会遇到问题。在钩子中输出内容,并在框架实际执行之前,以前可能有效,但在向前移动时将无效。

您可能会遇到问题的地方

// index.php
require 'vendor/autoload.php';

// just an example
define('START_TIME', microtime(true));

function hello() {
    echo 'Hello World';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // this will actually be fine
    echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});

Flight::before('start', function(){
    // things like this will cause an error
    echo '<html><head><title>My Page</title></head><body>';
});

Flight::route('/', function(){
    // this is actually just fine
    echo 'Hello World';

    // This should be just fine as well
    Flight::hello();
});

Flight::after('start', function(){
    // this will cause an error
    echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});

打开 v2 渲染行为

您是否仍然可以保持旧代码的编写方式而不必重写以使其与 v3 兼容?是的,您可以!您可以通过将 flight.v2.output_buffering 配置选项设置为 true 来打开 v2 渲染行为。这将允许您继续使用旧的渲染行为,但建议您在向前移动时修复它。在框架的 v4 中,这将被移除。

// index.php
require 'vendor/autoload.php';

Flight::set('flight.v2.output_buffering', true);

Flight::before('start', function(){
    // Now this will be just fine
    echo '<html><head><title>My Page</title></head><body>';
});

// more code 

调度器更改 (3.7.0)

如果您直接调用 Dispatcher 的静态方法,例如 Dispatcher::invokeMethod()Dispatcher::execute() 等,您需要更新代码以避免直接调用这些方法。Dispatcher 已经转换为更符合面向对象的方式,因此可以更轻松地使用依赖注入容器。如果需要调用类似于 Dispatcher 的方法,您可以手动使用类似于 $result = $class->$method(...$params);call_user_func_array() 的方式。

halt() stop() redirect()error() 更改 (3.10.0)

在 3.10.0 之前的默认行为是清除标头和响应主体。这已更改为仅清除响应主体。如果您需要清除标头,您可以使用 Flight::response()->clear()

Learn/configuration

配置

您可以通过使用set方法设置配置值来自定义Flight的某些行为。

Flight::set('flight.log_errors', true);

可用配置设置

以下是所有可用配置设置的列表:

加载器配置

此外,加载器还有另一个配置设置。这将允许您自动加载带有类名中的_的类。

// 启用带下划线的类加载
// 默认为true
Loader::$v2ClassLoading = false;

变量

Flight允许您保存变量,以便可以在应用程序的任何位置使用它们。

// 保存您的变量
Flight::set('id', 123);

// 在应用程序的其他位置
$id = Flight::get('id');

要查看变量是否已设置,您可以执行以下操作:

if (Flight::has('id')) {
  // 做某事
}

您可以通过执行以下操作来清除变量:

// 清除id变量
Flight::clear('id');

// 清除所有变量
Flight::clear();

Flight还使用变量进行配置目的。

Flight::set('flight.log_errors', true);

错误处理

错误和异常

Flight会捕获所有错误和异常,并将它们传递给error方法。默认行为是发送通用的HTTP 500 Internal Server Error响应以及一些错误信息。

您可以根据自己的需求覆盖此行为:

Flight::map('error', function (Throwable $error) {
  // 处理错误
  echo $error->getTraceAsString();
});

默认情况下,错误不会记录到Web服务器。您可以通过更改配置来启用此功能:

Flight::set('flight.log_errors', true);

未找到

当无法找到URL时,Flight会调用notFound方法。默认行为是发送一个带有简单消息的HTTP 404 Not Found响应。

您可以根据自己的需求覆盖此行为:

Flight::map('notFound', function () {
  // 处理未找到
});

Learn/security

安全

安全在网页应用程序中至关重要。您希望确保您的应用程序是安全的,并且您的用户数据是安全的。Flight 提供了许多功能来帮助您保护您的网页应用程序。

标头

HTTP 标头是保护您的网页应用程序最简单的方法之一。您可以使用标头来防止点击劫持、XSS 和其他攻击。您可以通过多种方式将这些标头添加到您的应用程序中。

检查您的标头安全性的两个很棒的网站分别是 securityheaders.comobservatory.mozilla.org

手动添加

您可以通过在 Flight\Response 对象上使用 header 方法手动添加这些标头。

// 设置 X-Frame-Options 标头以防止点击劫持
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// 设置内容安全策略标头以防止 XSS
// 注意:这个标头可能变得非常复杂,所以您可能需要在互联网上查找应用于您应用程序的示例
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

// 设置 X-XSS-Protection 标头以防止 XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// 设置 X-Content-Type-Options 标头以防止 MIME 嗅探
Flight::response()->header('X-Content-Type-Options', 'nosniff');

// 设置引用者策略标头以控制发送多少引用信息
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// 设置严格传输安全标头以强制使用 HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// 设置权限策略标头以控制可使用的功能和 API
Flight::response()->header('Permissions-Policy', 'geolocation=()');

这些可以添加到您的 bootstrap.phpindex.php 文件的顶部。

作为过滤器添加

您也可以将它们添加到过滤器/钩子中,如下所示:

// 在过滤器中添加标头
Flight::before('start', function() {
    Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
    Flight::response()->header("Content-Security-Policy", "default-src 'self'");
    Flight::response()->header('X-XSS-Protection', '1; mode=block');
    Flight::response()->header('X-Content-Type-Options', 'nosniff');
    Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
    Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
    Flight::response()->header('Permissions-Policy', 'geolocation=()');
});

作为中间件添加

您也可以将它们作为中间件类添加。这是保持您的代码清洁和有组织的好方法。

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

class SecurityHeadersMiddleware
{
    public function before(array $params): void
    {
        Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
        Flight::response()->header("Content-Security-Policy", "default-src 'self'");
        Flight::response()->header('X-XSS-Protection', '1; mode=block');
        Flight::response()->header('X-Content-Type-Options', 'nosniff');
        Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
        Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
        Flight::response()->header('Permissions-Policy', 'geolocation=()');
    }
}

// index.php 或者您设置路由的任何地方
// FYI,这个空字符串组充当全局中间件以
// 适用于所有路由。当然,您也可以做同样的事情,并且只需将
// 这些内容添加到特定路由。
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // 更多路由
}, [ new SecurityHeadersMiddleware() ]);

跨站请求伪造 (CSRF)

跨站请求伪造 (CSRF) 是一种攻击类型,恶意网站可以让用户的浏览器向您的网站发送请求。这可以用来在用户不知情的情况下在您的网站上执行操作。Flight 不提供内置的 CSRF 保护机制,但您可以通过使用中间件轻松实现自己的保护。

设置

首先,您需要生成一个 CSRF 令牌并将其存储在用户的会话中。然后您可以在表单中使用此令牌,并在提交表单时进行检查。

// 生成一个 CSRF 令牌并将其存储在用户的会话中
// (假设您已经创建了一个会话对象并将其附加到 Flight)
// 有关更多信息,请参阅会话文档
Flight::register('session', \Ghostff\Session\Session::class);

// 每个会话只需要生成一个令牌(这样可以跨多个标签页和请求为同一个用户工作)
if(Flight::session()->get('csrf_token') === null) {
    Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
<!-- 在您的表单中使用 CSRF 令牌 -->
<form method="post">
    <input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
    <!-- 其他表单字段 -->
</form>

使用 Latte

您还可以在 Latte 模板中设置一个自定义函数来输出 CSRF 令牌。

// 设置一个自定义函数以输出 CSRF 令牌
// 注意:已经配置了 View 以 Latte 作为视图引擎
Flight::view()->addFunction('csrf', function() {
    $csrfToken = Flight::session()->get('csrf_token');
    return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});

现在在您的 Latte 模板中,您可以使用 csrf() 函数来输出 CSRF 令牌。

<form method="post">
    {csrf()}
    <!-- 其他表单字段 -->
</form>

简短而简单是吧?

检查 CSRF 令牌

您可以使用事件过滤器检查 CSRF 令牌:

// 此中间件检查请求是否是 POST 请求,如果是,检查 CSRF 令牌是否有效
Flight::before('start', function() {
    if(Flight::request()->method == 'POST') {

        // 从表单数据中获取csrf令牌
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, 'Invalid CSRF token');
            // 或者用于 JSON 响应
            Flight::jsonHalt(['error' => 'Invalid CSRF token'], 403);
        }
    }
});

或者您可以使用一个中间件类:

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

class CsrfMiddleware
{
    public function before(array $params): void
    {
        if(Flight::request()->method == 'POST') {
            $token = Flight::request()->data->csrf_token;
            if($token !== Flight::session()->get('csrf_token')) {
                Flight::halt(403, 'Invalid CSRF token');
            }
        }
    }
}

// index.php 或者您设置路由的任何地方
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // 更多路由
}, [ new CsrfMiddleware() ]);

跨站脚本攻击 (XSS)

跨站脚本攻击 (XSS) 是一种攻击类型,恶意网站可以向您的网站注入代码。这些机会大多来自您的最终用户填写的表单值。您绝不能信任用户的输出!永远假设他们是世界上最好的黑客。他们可以向您的页面注入恶意 JavaScript 或 HTML。这段代码可以用来从您的用户那里窃取信息或在您的网站上执行操作。使用 Flight 的视图类,您可以轻松转义输出以防止 XSS 攻击。

// 假设用户很聪明并尝试将此用作他们的名字
$name = '<script>alert("XSS")</script>';

// 这将转义输出
Flight::view()->set('name', $name);
// 这将输出:&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// 如果您使用像 Latte 注册为您的视图类,它也会自动转义这个。
Flight::view()->render('模板', ['name' => $name]);

SQL 注入

SQL 注入是一种攻击类型,恶意用户可以向您的数据库注入 SQL 代码。这可以用来从您的数据库中窃取信息或在您的数据库上执行操作。再次强调,您绝不能信任用户的输入!永远假设他们是为了流血而来。您可以在您的 PDO 对象中使用预处理语句来防止 SQL 注入。

// 假设您已经将 Flight::db() 注册为您的 PDO 对象
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();

// 如果您使用 PdoWrapper 类,这可以很容易地在一行中完成
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

// 您也可以在含有 ? 占位符的 PDO 对象中执行相同的操作
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);

// 只是保证您永远不要像这样做...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// 因为如果 $username = "' OR 1=1; -- ";
// 在查询生成后看起来像这样
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// 看起来很奇怪,但它是一个有效的查询,会起作用。事实上,
// 这是一个非常常见的 SQL 注入攻击,将返回所有用户。

跨源资源共享 (CORS)

跨源资源共享 (CORS) 是一种允许网页中的许多资源(例如字体、JavaScript 等)从另一个域请求的机制。Flight 没有内置的功能, 但可以通过在 Flight::start() 方法调用之前运行一个钩子来轻松处理这个功能。

// app/utils/CorsUtil.php

namespace app\utils;

class CorsUtil
{
    public function set(array $params): void
    {
        $request = Flight::request();
        $response = Flight::response();
        if ($request->getVar('HTTP_ORIGIN') !== '') {
            $this->allowOrigins();
            $response->header('Access-Control-Allow-Credentials', 'true');
            $response->header('Access-Control-Max-Age', '86400');
        }

        if ($request->method === 'OPTIONS') {
            if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
                $response->header(
                    'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
                );
            }
            if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
                $response->header(
                    "Access-Control-Allow-Headers",
                    $request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
                );
            }

            $response->status(200);
            $response->send();
            exit;
        }
    }

    private function allowOrigins(): void
    {
        // 在这里自定义您允许的主机。
        $allowed = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        $request = Flight::request();

        if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
            $response = Flight::response();
            $response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
        }
    }
}

// index.php 或者您设置路由的任何地方
$CorsUtil = new CorsUtil();

// 这需要在 start 方法之前运行。
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

结论

安全性至关重要,确保您的网页应用程序是安全的非常重要。Flight 提供了许多功能来帮助您保护您的网页应用程序,但始终保持警惕并确保尽一切可能保护用户数据的安全性非常重要。始终假设最坏的情况,并且永远不要相信用户的输入。始终转义输出并使用准备语句来预防 SQL 注入。始终使用中间件保护您的路由免受 CSRF 和 CORS 攻击。如果您做了所有这些事情,那么构建安全的网页应用程序的道路将会变得更加平稳。

Learn/overriding

覆盖

Flight 允许您覆盖其默认功能以满足您自己的需求,而无需修改任何代码。

例如,当 Flight 无法将 URL 与路由匹配时,它会调用 notFound 方法,该方法发送一个通用的 HTTP 404 响应。您可以使用 map 方法覆盖此行为:

Flight::map('notFound', function() {
  // 显示自定义 404 页面
  include 'errors/404.html';
});

Flight 还允许您替换框架的核心组件。例如,您可以使用自定义类替换默认的 Router 类:

// 注册您的自定义类
Flight::register('router', MyRouter::class);

// 当 Flight 加载 Router 实例时,它将加载您的类
$myrouter = Flight::router();

但是,像 mapregister 这样的框架方法不能被覆盖。如果您尝试这样做,将会收到错误提示。

Learn/routing

路由

注意: 想了解更多关于路由的内容吗?查看更详细的解释,请访问"为什么要使用框架?"页面。

Flight中的基本路由是通过将URL模式与回调函数或类和方法的数组进行匹配来完成的。

Flight::route('/', function(){
    echo '你好,世界!';
});

路由匹配的顺序是按照定义的顺序进行的。第一个匹配请求的路由将被调用。

回调/函数

回调可以是任何可调用的对象。所以你可以使用一个常规函数:

function hello() {
    echo '你好,世界!';
}

Flight::route('/', 'hello');

你也可以使用类的静态方法:

class Greeting {
    public static function hello() {
        echo '你好,世界!';
    }
}

Flight::route('/', [ 'Greeting','hello' ]);

或者首先创建一个对象,然后调用方法:


// Greeting.php
class Greeting
{
    public function __construct() {
        $this->name = '张三';
    }

    public function hello() {
        echo "你好,{$this->name}!";
    }
}

// index.php
$greeting = new Greeting();

Flight::route('/', [ $greeting, 'hello' ]);
// 你还可以在不先创建对象的情况下做到这一点
// 注意:不会将参数注入到构造函数中
Flight::route('/', [ 'Greeting', 'hello' ]);
// 此外,你还可以使用这种更简洁的语法
Flight::route('/', 'Greeting->hello');
// 或者
Flight::route('/', Greeting::class.'->hello');

通过DIC(依赖注入容器)进行依赖注入

如果你想通过容器(PSR-11、PHP-DI、Dice等)进行依赖注入, 那么唯一可用的类型就是直接创建对象自己并使用容器来创建你的对象, 或者你可以使用字符串来定义要调用的类和方法。你可以查看依赖注入页面获取更多信息。

这里是一个快速示例:


use flight\database\PdoWrapper;

// Greeting.php
class Greeting
{
    protected PdoWrapper $pdoWrapper;
    public function __construct(PdoWrapper $pdoWrapper) {
        $this->pdoWrapper = $pdoWrapper;
    }

    public function hello(int $id) {
        // 使用$this->pdoWrapper做一些事情
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "你好,世界!我的名字是{$name}!";
    }
}

// index.php

// 使用你需要的任何参数设置容器
// 查看依赖注入页面以获取有关PSR-11的更多信息
$dice = new \Dice\Dice();

// 别忘记通过'$dice = '重新分配变量!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
    ]
]);

// 注册容器处理程序
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// 像正常一样设置路由
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// 或者
Flight::route('/hello/@id', 'Greeting->hello');
// 或者
Flight::route('/hello/@id', 'Greeting::hello');

Flight::start();

方法路由

默认情况下,路由模式将与所有请求方法进行匹配。你可以通过在URL之前放置一个标识符来响应特定方法。

Flight::route('GET /', function () {
  echo '我收到了一个GET请求。';
});

Flight::route('POST /', function () {
  echo '我收到了一个POST请求。';
});

// 你不能使用Flight::get()来设置路由,因为那是一个用于获取变量,而不是创建路由的方法。
// Flight::post('/', function() { /* 代码 */ });
// Flight::patch('/', function() { /* 代码 */ });
// Flight::put('/', function() { /* 代码 */ });
// Flight::delete('/', function() { /* 代码 */ });

你还可以通过使用|分隔符将多个方法映射到单个回调函数:

Flight::route('GET|POST /', function () {
  echo '我接收到了GET或POST请求。';
});

此外,你可以获得Router对象,该对象具有一些可供你使用的辅助方法:


$router = Flight::router();

// 映射所有方法
$router->map('/', function() {
    echo '你好,世界!';
});

// GET请求
$router->get('/users', function() {
    echo '用户';
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();

正则表达式

你可以在你的路由中使用正则表达式:

Flight::route('/user/[0-9]+', function () {
  // 这将匹配/user/1234
});

虽然这种方法是可用的,但建议使用命名参数或带有正则表达式的命名参数,因为它们更易读和更易于维护。

命名参数

你可以在你的路由中指定具有命名参数,这些参数将传递给你的回调函数。

Flight::route('/@name/@id', function (string $name, string $id) {
  echo "你好,$name($id)!";
});

你还可以通过使用:分隔符将带有命名参数的正则表达式与命名参数一起使用:

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // 这将匹配/bob/123
  // 但不匹配/bob/12345
});

注意: 不支持使用具有命名参数的正则表达式组()。: '(

可选参数

你可以指定命名参数为匹配的可选参数,方法是将段包裹在括号中。

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // 这将匹配以下URL:
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

任何未匹配的可选参数都将作为NULL传递。

通配符

匹配只会在单独的URL段上进行。如果你想匹配多个段,可以使用*通配符。

Flight::route('/blog/*', function () {
  // 这将匹配/blog/2000/02/01
});

要将所有请求路由到单个回调函数,可以这样做:

Flight::route('*', function () {
  // 做某事
});

传递

你可以通过从回调函数中返回true将执行传递给下一个匹配的路由。

Flight::route('/user/@name', function (string $name) {
  // 检查一些条件
  if ($name !== "Bob") {
    // 继续到下一个路由
    return true;
  }
});

Flight::route('/user/*', function () {
  // 这将被调用
});

路由别名

你可以为路由分配一个别名,以便稍后可以在代码中动态生成URL(例如模板)。

Flight::route('/users/@id', function($id) { echo '用户:'.$id; }, false, 'user_view');

// 稍后在代码中的某处
Flight::getUrl('user_view', [ 'id' => 5 ]); // 将返回'/users/5'

如果你的URL可能会改变,这会特别有帮助。在上面的示例中,假设用户已经移动到/admin/users/@id。 有了别名,你就不必更改任何引用别名的地方,因为别名现在将返回/admin/users/5,就像上面的示例一样。

路由别名也适用于组:

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo '用户:'.$id; }, false, 'user_view');
});

// 稍后在代码中的某处
Flight::getUrl('user_view', [ 'id' => 5 ]); // 将返回'/users/5'

路由信息

如果你想检查匹配的路由信息,你可以请求路由对象作为第三个参数传递给你的回调函数,通过将true作为路由方法的第三个参数传递。路由对象总是作为最后一个参数传递给回调函数。

Flight::route('/', function(\flight\net\Route $route) {
  // 与匹配的HTTP方法数组
  $route->methods;

  // 命名参数数组
  $route->params;

  // 匹配的正则表达式
  $route->regex;

  // 包含URL模式中使用的所有'*'的内容
  $route->splat;

  // 显示url路径...如果你真的需要它
  $route->pattern;

  // 显示为此路由分配的中间件
  $route->middleware;

  // 显示分配给此路由的别名
  $route->alias;
}, true);

路由分组

有时你想将相关路由分组在一起(例如/api/v1)。你可以通过使用group方法来实现:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // 匹配/api/v1/users
  });

  Flight::route('/posts', function () {
    // 匹配/api/v1/posts
  });
});

你甚至可以将组合的组合:

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get()获取变量,它不设置路由!查看下面的对象上下文
    Flight::route('GET /users', function () {
      // 匹配GET /api/v1/users
    });

    Flight::post('/posts', function () {
      // 匹配POST /api/v1/posts
    });

    Flight::put('/posts/1', function () {
      // 匹配PUT /api/v1/posts
    });
  });
  Flight::group('/v2', function () {

    // Flight::get()获取变量,它不会设置路由!查看下面的对象上下文
    Flight::route('GET /users', function () {
      // 匹配GET /api/v2/users
    });
  });
});

与对象上下文一起分组

你仍然可以在Engine对象中使用路由分组,方法如下:

$app = new \flight\Engine();
$app->group('/api/v1', function (Router $router) {

  // 使用$router变量
  $router->get('/users', function () {
    // 匹配GET /api/v1/users
  });

  $router->post('/posts', function () {
    // 匹配POST /api/v1/posts
  });
});

现在你可以使用streamWithHeaders()方法向客户端流式传输响应。这对于发送大型文件、长时间运行的流程或生成大型响应非常有用。流式传输路由的处理方式与普通路由略有不同。

注意: 仅当你将flight.v2.output_buffering设置为false时才可以使用流式传输响应。

带有手动标头的流

你可以通过在路由上使用stream()方法将响应流式传输给客户端。如果你这样做,你必须在向客户端输出任何内容之前手动设置所有方法。这是通过header() php函数或Flight::response()->setRealHeader()方法来完成的。

Flight::route('/@filename', function($filename) {

    // 显然,你将清理路径等内容。
    $fileNameSafe = basename($filename);

    // 如果你在路由执行后还需要设置其他标头
    // 你必须在输出任何内容之前定义它们。
    // 它们必须全部是对header()函数的原始调用
    // 或对Flight::response()->setRealHeader()的调用
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // 或
    Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="'.$fileNameSafe.'"');

    $fileData = file_get_contents('/some/path/to/files/'.$fileNameSafe);

    // 错误捕获等
    if(empty($fileData)) {
        Flight::halt(404, '文件未找到');
    }

    // 如果愿意,也可手动设置内容长度
    header('Content-Length: '.filesize($filename));

    // 将数据流式传输给客户端
    echo $fileData;

// 这就是这里的神奇之处
})->stream();

带有标头的流

你还可以使用streamWithHeaders()方法在开始流式传输之前设置标头。

Flight::route('/stream-users', function() {

    // 你可以在这里添加任何其他标头
    // 你只需使用header()或Flight::response()->setRealHeader()

    // 无论你如何获取数据,只是举例而已...
    $users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");

    echo '{';
    $user_count = count($users);
    while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
        echo json_encode($user);
        if(--$user_count > 0) {
            echo ',';
        }

        // 这是必需的,将数据发送到客户端
        ob_flush();
    }
    echo '}';

// 这是在开始流式传输之前设置标头的方法。
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    'Content-Disposition' => 'attachment; filename="users.json"',
    // 可选状态码,默认为200
    'status' => 200
]);

Learn/flight_vs_symfony

Flight 与 Symfony

什么是Symfony?

Symfony 是一组可重复使用的 PHP 组件和用于 Web 项目的 PHP 框架。

构建最佳 PHP 应用程序的标准基础。选择任何您自己应用程序所需的 50 个独立组件。

加速创建和维护您的 PHP Web 应用程序。结束重复的编码任务,享受控制代码的力量。

与 Flight 相比的优势

与 Flight 相比的缺点

Learn/flight_vs_another_framework

将Flight与另一个框架进行比较

如果您正在从另一个框架(如Laravel、Slim、Fat-Free或Symfony)迁移到Flight,则此页面将帮助您了解两者之间的区别。

Laravel

Laravel是一个功能齐全的框架,拥有所有功能和令人惊叹的开发人员专注生态系统,但需要在性能和复杂性方面付出代价。

查看Laravel和Flight之间的比较.

Slim

Slim是一个微框架,类似于Flight。它旨在轻量且易于使用,但可能比Flight复杂一些。

查看Slim和Flight之间的比较.

Fat-Free

Fat-Free是一个体积更小的全栈框架。虽然它拥有所有工具,但其数据架构可能使一些项目比必要复杂。

查看Fat-Free和Flight之间的比较.

Symfony

Symfony是一个模块化的企业级框架,旨在灵活且可扩展。对于较小的项目或新手开发人员,Symfony可能有些令人生畏。

查看Symfony和Flight之间的比较.

Learn/variables

变量

Flight允许您保存变量,以便它们可以在应用程序的任何地方使用。

// 保存变量
Flight::set('id', 123);

// 在应用程序的其他地方
$id = Flight::get('id');

要查看变量是否已设置,可以执行以下操作:

if (Flight::has('id')) {
  // 做些什么
}

您可以通过以下方式清除变量:

// 清除id变量
Flight::clear('id');

// 清除所有变量
Flight::clear();

Flight还使用变量进行配置目的。

Flight::set('flight.log_errors', true);

Learn/dependency_injection_container

依赖注入容器

介绍

依赖注入容器(DIC)是一个强大的工具,允许您管理应用程序的依赖关系。这是现代 PHP 框架中的一个关键概念,用于管理对象的实例化和配置。一些 DIC 库示例包括:Dice, Pimple, PHP-DI, 以及 league/container

DIC 是指以一种精致的方式让您在一个集中的位置创建和管理类。当您需要将同一个对象传递给多个类(例如您的控制器)时,这非常有用。一个简单的示例可能有助于更好地理解这一点。

基本示例

以前的做法可能类似这样:


require 'vendor/autoload.php';

// 用于从数据库中管理用户的类
class UserController {

    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

$User = new UserController(new PDO('mysql:host=localhost;dbname=test', 'user', 'pass'));
Flight::route('/user/@id', [ $UserController, 'view' ]);

Flight::start();

您可以从上面的代码中看到,我们正在创建一个新的 PDO 对象并将其传递给我们的 UserController 类。这对于一个小型应用程序来说是可以的,但随着应用程序的发展,您会发现在多个地方创建相同的 PDO 对象。这就是 DIC 发挥作用的地方。

以下是使用 DIC(使用 Dice)的相同示例:


require 'vendor/autoload.php';

// 与上例相同的类。未更改任何内容
class UserController {

    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

// 创建一个新容器
$container = new \Dice\Dice;
// 不要忘记像下面这样重新分配它给自己!
$container = $container->addRule('PDO', [
    // shared 意味着每次返回相同对象
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// 这会注册容器处理程序,以便 Flight 知道如何使用它。
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// 现在我们可以使用容器来创建我们的 UserController
Flight::route('/user/@id', [ 'UserController', 'view' ]);
// 或者您还可以像这样定义路由
Flight::route('/user/@id', 'UserController->view');
// 或者
Flight::route('/user/@id', 'UserController::view');

Flight::start();

您可能认为在示例中有很多额外的代码。其中的魔法之处在于当您有另一个需要 PDO 对象的控制器时。


// 如果您的所有控制器都有一个需要 PDO 对象的构造函数
// 下面的每个路由将自动注入它!!!
Flight::route('/company/@id', 'CompanyController->view');
Flight::route('/organization/@id', 'OrganizationController->view');
Flight::route('/category/@id', 'CategoryController->view');
Flight::route('/settings', 'SettingsController->view');

利用 DIC 的额外好处是进行单元测试变得更加简单。您可以创建一个模拟对象并将其传递给您的类。当您为应用程序编写测试时,这是一个巨大的好处!

PSR-11

Flight 还可以使用任何符合 PSR-11 的容器。这意味着您可以使用实现 PSR-11 接口的任何容器。以下是使用 League 的 PSR-11 容器的示例:


require 'vendor/autoload.php';

// 与上面相同的 UserController 类

$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::class)
    ->addArgument('mysql:host=localhost;dbname=test')
    ->addArgument('user')
    ->addArgument('pass');
Flight::registerContainerHandler($container);

Flight::route('/user', [ 'UserController', 'view' ]);

Flight::start();

与之前 Dice 示例相比,这可能会更冗长一些,但仍然可以以相同的好处完成工作!

自定义 DIC 处理程序

您还可以创建自己的 DIC 处理程序。如果您有一个不符合 PSR-11(Dice)的自定义容器,这会很有用。参见 基本示例 了解如何处理。

另外,在使用 Flight 时,还有一些有用的默认设置可以让您更轻松。

引擎实例

如果您在控制器/中间件中使用 Engine 实例,这是您配置它的方式:


// 在您的引导文件中的某处
$engine = Flight::app();

$container = new \Dice\Dice;
$container = $container->addRule('*', [
    'substitutions' => [
        // 这是您传递实例的位置
        Engine::class => $engine
    ]
]);

$engine->registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// 现在您可以在控制器/中间件中使用 Engine 实例

class MyController {
    public function __construct(Engine $app) {
        $this->app = $app;
    }

    public function index() {
        $this->app->render('index');
    }
}

添加其他类

如果您想要将其他类添加到容器中,使用 Dice 很容易,因为它们将自动由容器解析。以下是一个示例:


$container = new \Dice\Dice;
// 如果您不需要向您的类注入任何内容
// 您不需要定义任何内容!
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

class MyCustomClass {
    public function parseThing() {
        return 'thing';
    }
}

class UserController {

    protected MyCustomClass $MyCustomClass;

    public function __construct(MyCustomClass $MyCustomClass) {
        $this->MyCustomClass = $MyCustomClass;
    }

    public function index() {
        echo $this->MyCustomClass->parseThing();
    }
}

Flight::route('/user', 'UserController->index');

Learn/middleware

路由中间件

Flight支持路由和路由组中间件。中间件是在路由回调之前(或之后)执行的函数。这是在代码中添加API身份验证检查的绝佳方式,或者验证用户是否有权限访问路由。

基本中间件

这是一个基本示例:

// 如果只提供匿名函数,则将在路由回调之前执行。除类之外,没有“after”中间件函数(见下文)
Flight::route('/path', function() { echo 'Here I am!'; })->addMiddleware(function() {
    echo 'Middleware first!';
});

Flight::start();

// 这将输出“Middleware first! Here I am!”

在您使用中间件之前,请务必了解一些非常重要的内容:

中间件类

中间件也可以注册为类。如果需要“after”功能,必须使用类。

class MyMiddleware {
    public function before($params) {
        echo 'Middleware first!';
    }

    public function after($params) {
        echo 'Middleware last!';
    }
}

$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo 'Here I am! '; })->addMiddleware($MyMiddleware); // 也可以->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);

Flight::start();

// 这将显示“Middleware first! Here I am! Middleware last!”

处理中间件错误

假设您有一个授权中间件,并希望如果用户没有经过身份验证,则将用户重定向到登录页面。您有几个选择:

  1. 您可以从中间件函数中返回false,Flight将自动返回403 Forbidden错误,但无法自定义。
  2. 您可以使用Flight::redirect()将用户重定向到登录页面。
  3. 您可以在中间件中创建自定义错误,并停止路由的执行。

基本示例

这是一个简单的返回false; 示例:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            return false;
        }

        // 既然是true,一切都将继续进行
    }
}

重定向示例

这是将用户重定向到登录页面的示例:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            Flight::redirect('/login');
            exit;
        }
    }
}

自定义错误示例

假设您需要抛出JSON错误,因为您正在构建一个API。您可以这样做:

class MyMiddleware {
    public function before($params) {
        $authorization = Flight::request()->headers['Authorization'];
        if(empty($authorization)) {
            Flight::jsonHalt(['error' => 'You must be logged in to access this page.'], 403);
            // 或者
            Flight::json(['error' => 'You must be logged in to access this page.'], 403);
            exit;
            // 或者
            Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']);
        }
    }
}

分组中间件

您可以添加一个路由组,然后该组中的每个路由也将具有相同的中间件。如果您需要将一堆路由按照Auth中间件进行分组以检查头部中的API密钥,则这很有用。


// 添加到组方法的末尾
Flight::group('/api', function() {

    // 这个“空”外观的路由实际上将匹配/api
    Flight::route('', function() { echo 'api'; }, false, 'api');
    // 这将匹配/api/users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // 这将匹配/api/users/1234
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

如果要对所有路由应用全局中间件,可以添加一个“空”组:


// 添加到组方法的末尾
Flight::group('', function() {

    // 这仍然是/users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // 这仍然是/users/1234
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Learn/filtering

过滤

Flight 允许您在调用方法之前和之后对其进行过滤。无需记忆预定义的钩子。您可以过滤任何默认框架方法以及您映射的任何自定义方法。

过滤函数如下所示:

function (array &$params, string &$output): bool {
  // 过滤代码
}

使用传入的变量,您可以操作输入参数和/或输出。

您可以通过以下方式在调用方法之前运行过滤器:

Flight::before('start', function (array &$params, string &$output): bool {
  // 做一些事情
});

您可以通过以下方式在调用方法之后运行过滤器:

Flight::after('start', function (array &$params, string &$output): bool {
  // 做一些事情
});

您可以为任何方法添加任意数量的过滤器。它们将按照声明的顺序被调用。

以下是过滤过程的示例:

// 映射自定义方法
Flight::map('hello', function (string $name) {
  return "Hello, $name!";
});

// 添加一个前置过滤器
Flight::before('hello', function (array &$params, string &$output): bool {
  // 操作参数
  $params[0] = 'Fred';
  return true;
});

// 添加一个后置过滤器
Flight::after('hello', function (array &$params, string &$output): bool {
  // 操作输出
  $output .= " Have a nice day!";
  return true;
});

// 调用自定义方法
echo Flight::hello('Bob');

这应该显示:

Hello Fred! Have a nice day!

如果您定义了多个过滤器,可以通过在任何过滤器函数中返回 false 来中断链:

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'one';
  return true;
});

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'two';

  // 这将结束链
  return false;
});

// 这将不会被调用
Flight::before('start', function (array &$params, string &$output): bool {
  echo 'three';
  return true;
});

请注意,mapregister 等核心方法无法进行过滤,因为它们是直接调用而不是动态调用的。

Learn/requests

请求

Flight 将 HTTP 请求封装成一个单独的对象,可以通过以下方式访问:

$request = Flight::request();

典型用例

在 Web 应用程序中处理请求时,您通常会想提取一个头部,或 $_GET$_POST 参数,甚至可能是原始请求体。Flight 提供了一个简单的接口来完成所有这些操作。

以下是获取查询字符串参数的示例:

Flight::route('/search', function(){
    $keyword = Flight::request()->query['keyword'];
    echo "您正在搜索: $keyword";
    // 使用 $keyword 查询数据库或其他内容
});

以下是使用 POST 方法的表单示例:

Flight::route('POST /submit', function(){
    $name = Flight::request()->data['name'];
    $email = Flight::request()->data['email'];
    echo "您提交了: $name, $email";
    // 使用 $name 和 $email 保存到数据库或其他内容
});

请求对象属性

请求对象提供以下属性:

您可以将 query, data, cookies, 和 files 属性作为数组或对象访问。

因此,要获取查询字符串参数,可以执行:

$id = Flight::request()->query['id'];

或可以这样做:

$id = Flight::request()->query->id;

原始请求体

要获取原始 HTTP 请求体,例如在处理 PUT 请求时,可以执行:

$body = Flight::request()->getBody();

JSON 输入

如果您发送带有类型 application/json 和数据 {"id": 123} 的请求,它将可从 data 属性获得:

$id = Flight::request()->data->id;

$_GET

您可以通过 query 属性访问 $_GET 数组:

$id = Flight::request()->query['id'];

$_POST

您可以通过 data 属性访问 $_POST 数组:

$id = Flight::request()->data['id'];

$_COOKIE

您可以通过 cookies 属性访问 $_COOKIE 数组:

$myCookieValue = Flight::request()->cookies['myCookieName'];

$_SERVER

可以通过 getVar() 方法访问 $_SERVER 数组的快捷方式:


$host = Flight::request()->getVar['HTTP_HOST'];

通过 $_FILES 访问上传的文件

您可以通过 files 属性访问上传的文件:

$uploadedFile = Flight::request()->files['myFile'];

处理文件上传

您可以使用框架中的一些助手方法处理文件上传。基本上这归结为从请求中提取文件数据,并将其移动到新位置。

Flight::route('POST /upload', function(){
    // 如果您有一个输入字段,如 <input type="file" name="myFile">
    $uploadedFileData = Flight::request()->getUploadedFiles();
    $uploadedFile = $uploadedFileData['myFile'];
    $uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});

如果您上传了多个文件,可以遍历它们:

Flight::route('POST /upload', function(){
    // 如果您有一个输入字段,如 <input type="file" name="myFiles[]">
    $uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
    foreach ($uploadedFiles as $uploadedFile) {
        $uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
    }
});

安全提示: 始终验证和清理用户输入,尤其是在处理文件上传时。始终验证您允许上传的扩展名类型,但您还应该验证文件的“魔术字节”,以确保它实际上是用户声称的文件类型。有可用的 文章 来帮助处理这个问题。

请求头

您可以使用 getHeader()getHeaders() 方法访问请求头:


// 也许您需要授权头
$host = Flight::request()->getHeader('Authorization');
// 或者
$host = Flight::request()->header('Authorization');

// 如果您需要获取所有头
$headers = Flight::request()->getHeaders();
// 或者
$headers = Flight::request()->headers();

请求体

您可以使用 getBody() 方法访问原始请求体:

$body = Flight::request()->getBody();

请求方法

您可以使用 method 属性或 getMethod() 方法访问请求方法:

$method = Flight::request()->method; // 实际上调用 getMethod()
$method = Flight::request()->getMethod();

注意: getMethod() 方法首先从 $_SERVER['REQUEST_METHOD'] 提取方法,然后可以通过 $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] 覆盖它(如果存在),或者通过 $_REQUEST['_method'](如果存在)覆盖它。

请求 URL

有几个助手方法可以组合 URL 的不同部分,以方便您使用。

完整 URL

您可以使用 getFullUrl() 方法访问完整请求 URL:

$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar

基础 URL

您可以使用 getBaseUrl() 方法访问基础 URL:

$url = Flight::request()->getBaseUrl();
// 注意,末尾没有斜杠。
// https://example.com

查询解析

您可以将 URL 传递给 parseQuery() 方法,以将查询字符串解析为关联数组:

$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']

Learn/frameworkmethods

# 框架方法

Flight 旨在易于使用和理解。以下是框架的完整方法集。
它包括核心方法,这些是常规静态方法,以及可被筛选或覆盖的可扩展方法,这些是映射方法。

## 核心方法

```php
Flight::map(string $name, callable $callback, bool $pass_route = false) // 创建自定义框架方法。
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // 将类注册到框架方法。
Flight::before(string $name, callable $callback) // 在调用框架方法前添加筛选器。
Flight::after(string $name, callable $callback) // 在调用框架方法后添加筛选器。
Flight::path(string $path) // 添加自动加载类的路径。
Flight::get(string $key) // 获取变量。
Flight::set(string $key, mixed $value) // 设置变量。
Flight::has(string $key) // 检查变量是否设置。
Flight::clear(array|string $key = []) // 清除变量。
Flight::init() // 将框架初始化为默认设置。
Flight::app() // 获取应用程序对象实例

可扩展方法

Flight::start() // 启动框架。
Flight::stop() // 停止框架并发送响应。
Flight::halt(int $code = 200, string $message = '') // 停止框架,可选择性地附带状态代码和消息。
Flight::route(string $pattern, callable $callback, bool $pass_route = false) // 将 URL 模式映射到回调。
Flight::group(string $pattern, callable $callback) // 为 URL 创建分组,模式必须为字符串。
Flight::redirect(string $url, int $code) // 重定向到另一个 URL。
Flight::render(string $file, array $data, ?string $key = null) // 渲染模板文件。
Flight::error(Throwable $error) // 发送 HTTP 500 响应。
Flight::notFound() // 发送 HTTP 404 响应。
Flight::etag(string $id, string $type = 'string') // 执行 ETag HTTP 缓存。
Flight::lastModified(int $time) // 执行上次修改的 HTTP 缓存。
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // 发送 JSON 响应。
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // 发送 JSONP 响应。

mapregister 添加的任何自定义方法也可进行筛选。

Learn/api

# 框架 API 方法

Flight 旨在易于使用和理解。以下是框架的完整方法集。它包括核心方法,这些是常规静态方法,以及可扩展方法,这些是可以进行过滤或覆盖的映射方法。

## 核心方法

这些方法是框架的核心,不可被覆盖。

```php
Flight::map(string $name, callable $callback, bool $pass_route = false) // 创建自定义框架方法。
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // 将类注册到框架方法。
Flight::unregister(string $name) // 从框架方法中取消注册类。
Flight::before(string $name, callable $callback) // 在框架方法之前添加过滤器。
Flight::after(string $name, callable $callback) // 在框架方法之后添加过滤器。
Flight::path(string $path) // 添加一个用于自动加载类的路径。
Flight::get(string $key) // 获取由 Flight::set() 设置的变量。
Flight::set(string $key, mixed $value) // 设置 Flight 引擎中的变量。
Flight::has(string $key) // 检查变量是否已设置。
Flight::clear(array|string $key = []) // 清除变量。
Flight::init() // 将框架初始化为其默认设置。
Flight::app() // 获取应用对象实例
Flight::request() // 获取请求对象实例
Flight::response() // 获取响应对象实例
Flight::router() // 获取路由器对象实例
Flight::view() // 获取视图对象实例

可扩展方法

Flight::start() // 启动框架。
Flight::stop() // 停止框架并发送响应。
Flight::halt(int $code = 200, string $message = '') // 使用可选状态代码和消息停止框架。
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // 将 URL 模式映射到回调。
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // 将 POST 请求的 URL 模式映射到回调。
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // 将 PUT 请求的 URL 模式映射到回调。
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // 将 PATCH 请求的 URL 模式映射到回调。
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // 将 DELETE 请求的 URL 模式映射到回调。
Flight::group(string $pattern, callable $callback) // 为 URL 创建分组,模式必须是一个字符串。
Flight::getUrl(string $name, array $params = []) // 根据路由别名生成 URL。
Flight::redirect(string $url, int $code) // 重定向到另一个 URL。
Flight::download(string $filePath) // 下载文件。
Flight::render(string $file, array $data, ?string $key = null) // 渲染模板文件。
Flight::error(Throwable $error) // 发送 HTTP 500 响应。
Flight::notFound() // 发送 HTTP 404 响应。
Flight::etag(string $id, string $type = 'string') // 执行 ETag HTTP 缓存。
Flight::lastModified(int $time) // 执行最后修改的 HTTP 缓存。
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // 发送 JSON 响应。
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // 发送 JSONP 响应。
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // 发送 JSON 响应并停止框架。

使用 mapregister 添加的任何自定义方法也可以进行过滤。有关如何映射这些方法的示例,请参阅 扩展 Flight 指南。

Learn/why_frameworks

为什么使用一个框架?

一些程序员强烈反对使用框架。他们认为框架臃肿、缓慢且难以学习。 他们说框架是不必要的,你可以不用框架编写更好的代码。 关于使用框架的缺点有一些合理的观点。然而,使用框架也有许多优势。

使用框架的原因

下面是您可能考虑使用框架的一些原因:

Flight 是一个微框架。这意味着它又小又轻量。它的功能不及像 Laravel 或 Symfony 这样的大型框架多。 然而,它确实提供了构建 Web 应用程序所需的许多功能。而且学习和使用它也很容易。 这使其成为迅速轻松构建 Web 应用程序的不错选择。如果您对框架还不熟悉,Flight 是一个很好的开始框架。 它将帮助您了解使用框架的优势,而不会让您在太复杂的内容中迷失方向。 在您有了使用 Flight 的经验后,将更容易转向像 Laravel 或 Symfony 这样更复杂的框架, 但 Flight 仍然可以构建成功的强大应用程序。

什么是路由?

路由是 Flight 框架的核心,但究竟是什么呢?路由是将 URL 与代码中的特定功能匹配的过程。 这是您可以根据被请求的 URL 使您的网站执行不同操作的方法。例如,当用户访问 /user/1234 时,您可能希望显示用户的个人资料, 但当他们访问 /users 时显示所有用户的列表。所有这些都通过路由完成。

可能像这样运作:

  1. 用户转到您的浏览器并键入 http://example.com/user/1234
  2. 服务器收到请求,检查 URL 并将其传递到您的 Flight 应用程序代码。
  3. 假设在您的 Flight 代码中有类似 Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]); 这样的东西。 您的 Flight 应用程序代码检查 URL 并看到它匹配您定义的路由,然后运行为该路由定义的代码。
  4. 然后 Flight 路由将运行并调用 UserController 类中的 viewUserProfile($id) 方法,将 1234 作为 $id 参数传入该方法。
  5. 您的 viewUserProfile() 方法中的代码将运行并执行您告诉它要执行的操作。 您可能会输出一些用户资料页的 HTML,或者如果这是一个 RESTful API,则可能打印出包含用户信息的 JSON 响应。
  6. Flight 将其整理起来,生成响应头并将其发送回用户的浏览器。
  7. 用户充满喜悦,自我给自己一个温暖的拥抱!

为什么重要?

拥有一个合适的中心化路由器实际上会大大简化您的生活!起初可能有点难以看到。以下是一些原因:

可能您熟悉逐个脚本的方式创建网站。您可能有一个名为 index.php 的文件,其中包含一堆 if 语句,以检查 URL,然后根据 URL 运行特定函数。 这是一种形式的路由,但不够有组织并且很快就会失控。Flight 的路由系统是一种更有组织和强大的处理路由的方式。

这样?


// /user/view_profile.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    viewUserProfile($id);
}

// /user/edit_profile.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    editUserProfile($id);
}

// 等等...

还是这样?


// index.php
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);
Flight::route('/user/@id/edit', [ 'UserController', 'editUserProfile' ]);

// 可能在您的 app/controllers/UserController.php 中
class UserController {
    public function viewUserProfile($id) {
        // 做一些事情
    }

    public function editUserProfile($id) {
        // 做一些事情
    }
}

希望您开始看到使用中心化路由系统的好处。长期而言,这将更易于管理和理解!

请求与响应

Flight 提供了一种简单易用的方式处理请求和响应。这是 Web 框架的核心。它接收来自用户浏览器的请求, 处理它,然后发送回响应。这就是您可以构建显示用户资料、让用户登录或让用户发布新博客文章等功能的 Web 应用程序的方法。

请求

当用户访问您的网站时,请求是用户的浏览器发送到服务器的内容。这个请求包含关于用户想要执行的操作的信息。 例如,它可能包含关于用户想要访问的 URL、用户想要发送到您的服务器或从用户那里接收的数据的信息。重要的是要知道请求是只读的。 您无法更改请求,但可以从中读取数据。

Flight 提供了一种简单的方法来访问有关请求的信息。您可以使用 Flight::request() 方法来访问请求的信息。 此方法返回一个包含请求信息的 Request 对象。您可以使用这个对象来访问请求的信息,例如 URL、方法或用户发送到您的服务器的数据。

响应

当用户访问您的网站时,服务器发送给用户浏览器的内容就是响应。这个响应包含关于服务器要执行的操作的信息。 例如,它可能包含关于服务器想要发送给用户的数据、服务器想要从用户那里接收的数据或服务器想要存储在用户计算机上的数据的信息。

Flight 提供了一种简单的方法将响应发送给用户的浏览器。您可以使用 Flight::response() 方法发送响应。 此方法接受一个 Response 对象作为参数,并将响应发送给用户的浏览器。您可以使用这个对象将响应发送给用户的浏览器,例如 HTML、JSON 或文件。 Flight 可以帮助您自动生成响应的某些部分,使事情变得容易,但最终您可以控制发送给用户的内容。

Learn/httpcaching

HTTP 缓存

Flight 提供了内置支持,用于 HTTP 级别的缓存。如果满足缓存条件,Flight 将返回一个 HTTP 304 Not Modified 响应。下一次客户端请求相同资源时,它们将被提示使用本地缓存版本。

上次修改时间

您可以使用 lastModified 方法并传入一个 UNIX 时间戳来设置页面上次修改的日期和时间。客户端将继续使用它们的缓存,直到上次修改的值被更改。

Flight::route('/news', function () {
  Flight::lastModified(1234567890);
  echo '此内容将被缓存。';
});

ETag

ETag 缓存类似于 Last-Modified,不同之处在于您可以为资源指定任何想要的 id:

Flight::route('/news', function () {
  Flight::etag('my-unique-id');
  echo '此内容将被缓存。';
});

请记住,调用 lastModifiedetag 都将设置并检查缓存值。如果在请求之间的缓存值相同,则 Flight 将立即发送一个 HTTP 304 响应并停止处理。

Learn/responses

响应

Flight 帮助为您生成部分响应标头,但您可以控制向用户发送什么内容。有时,您可以直接访问Response对象,但大多数情况下,您将使用Flight实例发送响应。

发送基本响应

Flight 使用 ob_start() 缓冲输出。这意味着您可以使用 echoprint 向用户发送响应,Flight 将捕获它并使用适当的标头发送回用户。


// 这将向用户的浏览器发送 "Hello, World!"
Flight::route('/', function() {
    echo "Hello, World!";
});

// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!

作为替代,您还可以调用 write() 方法来添加到主体。


// 这将向用户的浏览器发送 "Hello, World!"
Flight::route('/', function() {
    // 冗长,但在需要时有时会起作用
    Flight::response()->write("Hello, World!");

    // 如果您想要检索此时设置的主体
    // 您可以这样做
    $body = Flight::response()->getBody();
});

状态码

您可以使用status方法设置响应的状态码:

Flight::route('/@id', function($id) {
    if($id == 123) {
        Flight::response()->status(200);
        echo "Hello, World!";
    } else {
        Flight::response()->status(403);
        echo "Forbidden";
    }
});

如果要获取当前状态码,可以使用不带参数的status方法:

Flight::response()->status(); // 200

设置响应主体

您可以使用 write 方法设置响应主体,但是,如果您使用 echoprint 任何内容,它将被捕获并通过输出缓冲发送为响应主体。

Flight::route('/', function() {
    Flight::response()->write("Hello, World!");
});

// 同样的

Flight::route('/', function() {
    echo "Hello, World!";
});

清除响应主体

如果要清除响应主体,可以使用 clearBody 方法:

Flight::route('/', function() {
    if($someCondition) {
        Flight::response()->write("Hello, World!");
    } else {
        Flight::response()->clearBody();
    }
});

在响应主体上运行回调

您可以使用 addResponseBodyCallback 方法在响应主体上运行回调:

Flight::route('/users', function() {
    $db = Flight::db();
    $users = $db->fetchAll("SELECT * FROM users");
    Flight::render('users_table', ['users' => $users]);
});

// 这将为任何路由的所有响应执行gzip
Flight::response()->addResponseBodyCallback(function($body) {
    return gzencode($body, 9);
});

您可以添加多个回调,它们将按添加顺序运行。由于这可以接受任何 callable,它可以接受类数组 [ $class, 'method' ],闭包 $strReplace = function($body) { str_replace('hi', 'there', $body); };,或者函数名称 'minify',如果例如您有一个用于缩小您的html代码的函数。

注意: 如果使用flight.v2.output_buffering配置选项,则路由回调将无法正常工作。

特定路由回调

如果希望仅应用于特定路由,可以在路由本身中添加回调:

Flight::route('/users', function() {
    $db = Flight::db();
    $users = $db->fetchAll("SELECT * FROM users");
    Flight::render('users_table', ['users' => $users]);

    // 这将仅对此路由的响应进行gzip
    Flight::response()->addResponseBodyCallback(function($body) {
        return gzencode($body, 9);
    });
});

中间件选项

您还可以使用中间件通过中间件将回调应用于所有路由:

// MinifyMiddleware.php
class MinifyMiddleware {
    public function before() {
        // 在此处在 response() 对象上应用回调。
        Flight::response()->addResponseBodyCallback(function($body) {
            return $this->minify($body);
        });
    }

    protected function minify(string $body): string {
        // 以某种方式缩小主体
        return $body;
    }
}

// index.php
Flight::group('/users', function() {
    Flight::route('', function() { /* ... */ });
    Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);

设置响应标头

您可以使用header方法设置响应的内容类型等标头:


// 这将以纯文本形式向用户的浏览器发送 "Hello, World!"
Flight::route('/', function() {
    Flight::response()->header('Content-Type', 'text/plain');
    // 或
    Flight::response()->setHeader('Content-Type', 'text/plain');
    echo "Hello, World!";
});

JSON

Flight 提供支持发送 JSON 和 JSONP 响应。要发送 JSON 响应,您需要传递要进行 JSON 编码的一些数据:

Flight::json(['id' => 123]);

具有状态码的 JSON

您还可以将状态码作为第二个参数传递:

Flight::json(['id' => 123], 201);

具有漂亮打印的 JSON

您还可以在最后一个位置传入参数以启用漂亮打印:

Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);

如果正在更改传递给Flight::json()的选项并希望使用更简单的语法,可以重新映射JSON方法:

Flight::map('json', function($data, $code = 200, $options = 0) {
    Flight::_json($data, $code, true, 'utf-8', $options);
});

// 现在可以这样使用
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);

JSON 和停止执行(v3.10.0)

如果要发送 JSON 响应并停止执行,可以使用 jsonHalt 方法。这在您检查某种授权类型,如果用户未经授权,您可以立即发送 JSON 响应,清除已有主体内容并停止执行时非常有用。

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // 检查用户是否已获授权
    if($authorized === false) {
        Flight::jsonHalt(['error' => '未经授权'], 401);
    }

    // 继续处理其余路由
});

在 v3.10.0 之前,您可能需要执行以下操作:

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // 检查用户是否已获授权
    if($authorized === false) {
        Flight::halt(401, json_encode(['error' => '未经授权']));
    }

    // 继续处理其余路由
});

JSONP

对于 JSONP 请求,您可以选择传入用于定义回调函数的查询参数名称:

Flight::jsonp(['id' => 123], 'q');

因此,使用 ?q=my_func 发出 GET 请求时,您应该接收以下输出:

my_func({"id":123});

如果不传入查询参数名称,它将默认为 jsonp

重定向至另一个 URL

您可以使用 redirect() 方法重定向当前请求,并传递一个新的 URL:

Flight::redirect('/new/location');

Flight 默认发送 HTTP 303("查看其他")状态码。您还可以选择设置自定义代码:

Flight::redirect('/new/location', 401);

停止

您可以在任何时候通过调用 halt 方法停止框架:

Flight::halt();

您还可以指定可选的 HTTP 状态码和消息:

Flight::halt(200, '马上回来...');

调用 halt 将放弃到该点为止的任何响应内容。如果要停止框架并输出当前响应,使用 stop 方法:

Flight::stop();

清除响应数据

您可以使用 clear() 方法清除响应主体和标头。这将清除分配给响应的任何标头,清除响应主体,并将状态码设置为 200

Flight::response()->clear();

仅清除响应主体

如果只想清除响应主体,可以使用 clearBody() 方法:

// 这仍将保留在 response() 对象上设置的任何标头。
Flight::response()->clearBody();

HTTP 缓存

Flight 提供内置支持的 HTTP 级缓存。如果满足缓存条件,Flight 将返回 HTTP 304 未修改 响应。下次客户端请求同一资源时,它们将提示使用其本地缓存版本。

路由级别缓存

如果要缓存整个响应,可以使用 cache() 方法并传递缓存时间。


// 这将为 5 分钟缓存响应
Flight::route('/news', function () {
  Flight::response()->cache(time() + 300);
  echo '此内容将被缓存。';
});

// 或者,您可以使用您将传递给 strtotime() 方法的字符串
Flight::route('/news', function () {
  Flight::response()->cache('+5 minutes');
  echo '此内容将被缓存。';
});

上次修改

您可以使用 lastModified 方法并传递 UNIX 时间戳来设置页面上次修改的日期和时间。直到最后修改值更改为止,客户端将继续使用其缓存。

Flight::route('/news', function () {
  Flight::lastModified(1234567890);
  echo '此内容将被缓存。';
});

ETag

ETag 缓存类似于 Last-Modified,只是您可以为资源指定任何 id:

Flight::route('/news', function () {
  Flight::etag('my-unique-id');
  echo '此内容将被缓存。';
});

请记住,调用 lastModifiedetag 将同时设置和检查缓存值。如果在请求之间缓存值相同,则 Flight 将立即发送一个 HTTP 304 响应并停止处理。

下载文件

有一个用于下载文件的辅助方法。您可以使用download方法并传递路径。

Flight::route('/download', function () {
  Flight::download('/path/to/file.txt');
});

Learn/frameworkinstance

框架实例

将 Flight 作为全局静态类运行,您可以选择将其作为对象实例运行。

require 'flight/autoload.php';

$app = Flight::app();

$app->route('/', function () {
  echo 'hello world!';
});

$app->start();

因此,您可以通过引擎对象上具有相同名称的实例方法来调用实例方法,而不是调用静态方法。

Learn/redirects

重定向

您可以使用redirect方法并传入新的URL来重定向当前请求:

Flight::redirect('/new/location');

默认情况下,Flight发送HTTP 303状态码。您还可以选择设置自定义代码:

Flight::redirect('/new/location', 401);

Learn/views

视图

Flight默认提供一些基本的模板功能。要显示视图模板,请调用render方法并提供模板文件名称以及可选的模板数据:

Flight::render('hello.php', ['name' => 'Bob']);

您传递的模板数据将自动注入到模板中,并且可以像本地变量一样引用。模板文件只是简单的PHP文件。如果hello.php模板文件的内容是:

Hello, <?= $name ?>!

输出将是:

Hello, Bob!

您还可以通过使用set方法手动设置视图变量:

Flight::view()->set('name', 'Bob');

现在名为name的变量可以在所有视图中使用。因此,您只需简单地执行:

Flight::render('hello');

请注意,在render方法中指定模板名称时,可以省略.php扩展名。

默认情况下,Flight将在views目录中查找模板文件。您可以通过设置以下配置来为您的模板设置替代路径:

Flight::set('flight.views.path', '/path/to/views');

布局

网站通常具有一个带有可互换内容的单个布局模板文件。要呈现要在布局中使用的内容,您可以向render方法传递一个可选参数。

Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');

然后,您的视图将保存名为headerContentbodyContent的变量。然后,您可以通过执行以下操作来呈现您的布局:

Flight::render('layout', ['title' => 'Home Page']);

如果模板文件如下所示:

header.php

<h1><?= $heading ?></h1>

body.php

<div><?= $body ?></div>

layout.php

<html>
  <head>
    <title><?= $title ?></title>
  </head>
  <body>
    <?= $headerContent ?>
    <?= $bodyContent ?>
  </body>
</html>

输出将是:

<html>
  <head>
    <title>Home Page</title>
  </head>
  <body>
    <h1>Hello</h1>
    <div>World</div>
  </body>
</html>

自定义视图

Flight允许您通过注册自己的视图类简单地更换默认视图引擎。以下是如何为视图使用Smarty模板引擎的示例:

// 加载Smarty库
require './Smarty/libs/Smarty.class.php';

// 将Smarty注册为视图类
// 还要传递回调函数以在加载时配置Smarty
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
  $smarty->setCacheDir('./cache/');
});

// 设置模板数据
Flight::view()->assign('name', 'Bob');

// 显示模板
Flight::view()->display('hello.tpl');

为了完整起见,您还应该覆盖Flight的默认render方法:

Flight::map('render', function(string $template, array $data): void {
  Flight::view()->assign($data);
  Flight::view()->display($template);
});

Learn/templates

HTML视图和模板

Flight默认提供一些基本的模板功能。

如果您需要更复杂的模板需求,请参阅自定义视图部分中的Smarty和Latte示例。

默认视图引擎

要显示视图模板,请调用render方法,传入模板文件名称和可选的模板数据:

Flight::render('hello.php', ['name' => 'Bob']);

您传入的模板数据将自动注入到模板中,可以像使用本地变量一样引用。模板文件简单地是PHP文件。如果hello.php模板文件的内容是:

Hello, <?= $name ?>!

输出将是:

Hello, Bob!

您还可以通过使用set方法手动设置视图变量:

Flight::view()->set('name', 'Bob');

现在变量name可以在所有视图中使用。因此,您可以简单地执行:

Flight::render('hello');

请注意,在render方法中指定模板的名称时,可以省略.php扩展名。

默认情况下,Flight将在views目录中查找模板文件。您可以通过设置以下配置来为模板设置另一个路径:

Flight::set('flight.views.path', '/path/to/views');

布局

网站通常具有一个具有可交换内容的单个布局模板文件。要呈现用于布局的内容,可以将一个可选参数传递给render方法。

Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');

然后,您的视图将保存名为headerContentbodyContent的变量。然后,您可以通过执行以下操作来呈现您的布局:

Flight::render('layout', ['title' => '主页']);

如果模板文件如下所示:

header.php:

<h1><?= $heading ?></h1>

body.php:

<div><?= $body ?></div>

layout.php:

<html>
  <head>
    <title><?= $title ?></title>
  </head>
  <body>
    <?= $headerContent ?>
    <?= $bodyContent ?>
  </body>
</html>

输出将是:

<html>
  <head>
    <title>主页</title>
  </head>
  <body>
    <h1>Hello</h1>
    <div>World</div>
  </body>
</html>

自定义视图引擎

Flight允许您通过注册自己的视图类来简单地更换默认视图引擎。

Smarty

以下是如何在视图中使用Smarty模板引擎:

// 加载Smarty库
require './Smarty/libs/Smarty.class.php';

// 注册Smarty为视图类
// 还要传递一个回调函数来在加载时配置Smarty
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
});

// 分配模板数据
Flight::view()->assign('name', 'Bob');

// 显示模板
Flight::view()->display('hello.tpl');

为完整起见,您还应该覆盖Flight的默认render方法:

Flight::map('render', function(string $template, array $data): void {
  Flight::view()->assign($data);
  Flight::view()->display($template);
});

Latte

以下是如何在视图中使用Latte模板引擎:


// 注册Latte为视图类
// 并传递一个回调函数来在加载时配置Latte
Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $latte) {
  // 这是Latte将缓存您的模板以加快速度的地方
    // Latte的一个很好的特性是,当您对模板进行更改时,它会自动刷新您的缓存!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // 告诉Latte您的视图的根目录在哪里
    $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/'));
});

// 并封装以便您可以正确使用Flight::render()
Flight::map('render', function(string $template, array $data): void {
  // 这就像$latte_engine->render($template, $data);
  echo Flight::view()->render($template, $data);
});

Learn/flight_vs_fat_free

Flight vs Fat-Free

什么是Fat-Free?

Fat-Free(亲切地被称为 F3)是一个强大而易于使用的PHP微框架,旨在帮助您快速构建动态和健壮的Web应用程序!

Flight在许多方面与Fat-Free进行比较,可能是功能和简单性方面最接近的近亲。 Fat-Free具有许多Flight没有的功能,但也具有Flight具有的许多功能。 Fat-Free开始显露岁月的痕迹,不再像以前那样受欢迎。

更新频率正在变低,社区也不再像以前那样活跃。代码足够简单,但有时缺乏语法纪律可能会使阅读和理解变得困难。它可以用于PHP 8.3,但代码本身看起来仍然像生存在PHP 5.3中。

与Flight相比的优势

与Flight相比的缺点

Learn/extending

扩展

Flight 旨在成为一个可扩展的框架。该框架提供了一组默认方法和组件,但允许您映射自己的方法,注册自己的类,甚至覆盖现有的类和方法。

如果您正在寻找 DIC(依赖注入容器),请转到依赖注入容器 页面。

映射方法

您可以使用 map 函数来映射自己的简单自定义方法:

// 映射您的方法
Flight::map('hello', function (string $name) {
  echo "你好,$name!";
});

// 调用您的自定义方法
Flight::hello('Bob');

尽管可以创建简单的自定义方法,但建议仅在 PHP 中创建标准函数。这样可以在 IDE 中获得自动补全,并且更容易阅读。 上述代码的等效版本如下:

function hello(string $name) {
  echo "你好,$name!";
}

hello('Bob');

当您需要将变量传递给您的方法以获取预期的值时,这更常用。像下面这样使用 register() 方法更适合传递配置,然后调用您预先配置的类。

注册类

要注册自己的类并对其进行配置,您可以使用 register 函数:

// 注册您的类
Flight::register('user', User::class);

// 获得您类的实例
$user = Flight::user();

register 方法还允许您将参数传递给您的类构造函数。因此,当加载您的自定义类时,它将预先初始化。 通过传入一个额外的数组来定义构造函数参数。 这是加载数据库连接的示例:

// 带构造函数参数注册类
Flight::register('db', PDO::class,['mysql:host=localhost;dbname=test', 'user', 'pass']);

// 获得您类的实例
// 这将使用定义的参数创建一个对象
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();

// 如果以后在您的代码中需要它,只需再次调用相同的方法
class SomeController {
  public function __construct() {
    $this->db = Flight::db();
  }
}

如果传递了额外的回调参数,它将在类构造后立即执行。 这允许您为新对象执行任何设置过程。回调函数接受一个参数,即新对象的实例。

// 将被构造的对象传递给回调函数
Flight::register(
  'db',
  PDO::class,
  ['mysql:host=localhost;dbname=test', 'user', 'pass'],
  function (PDO $db) {
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  }
);

默认情况下,每次加载您的类时,您将获得一个共享实例。 要获取类的新实例,只需将 false 作为参数传递即可:

// 类的共享实例
$shared = Flight::db();

// 类的新实例
$new = Flight::db(false);

请记住,映射的方法优先于注册的类。如果您使用相同名称声明两者,那么只会调用映射的方法。

覆盖框架方法

Flight 允许您覆盖其默认功能以满足您自己的需求,而无需修改任何代码。您可以查看所有可覆盖的方法此处

例如,当 Flight 无法将 URL 与路由匹配时,它将调用 notFound 方法,后者发送通用的 HTTP 404 响应。您可以通过使用 map 方法覆盖此行为:

Flight::map('notFound', function() {
  // 显示自定义 404 页面
  include 'errors/404.html';
});

Flight 还允许您替换框架的核心组件。 例如,您可以使用自己的自定义类替换默认的 Router 类:

// 注册您的自定义类
Flight::register('router', MyRouter::class);

// 当 Flight 加载 Router 实例时,它将加载您的类
$myrouter = Flight::router();

但是,mapregister 等框架方法无法被覆盖。如果尝试这样做,将会收到错误。

Learn/json

JSON

Flight提供发送JSON和JSONP响应的支持。要发送JSON响应,您需要传递一些数据进行JSON编码:

Flight::json(['id' => 123]);

对于JSONP请求,您可以选择传递用于定义回调函数的查询参数名称:

Flight::jsonp(['id' => 123], 'q');

因此,当使用 ?q=my_func 发出GET请求时,您应该收到以下输出:

my_func({"id":123});

如果您没有传递查询参数名称,它将默认为 jsonp

Learn/flight_vs_slim

Flight 与 Slim

什么是Slim?

Slim 是一个 PHP 微框架,帮助您快速编写简单而强大的 Web 应用程序和 API。

实际上,Flight 的一些 v3 特性受到了 Slim 的启发。分组路由和按特定顺序执行中间件是两个受 Slim 启发的特性。Slim v3 的出现旨在追求简单性,不过关于 v4,评价褒贬不一

与Flight相比的优点

与Flight相比的缺点

Learn/autoloading

自動加載

自動加載是 PHP 中的一個概念,在這裡您指定要從哪些目錄加載類。這比使用requireinclude來加載類要好得多。這也是使用 Composer 套件的要求。

默認情況下,任何Flight類都會由 Composer 自動加載。但是,如果您想要自動加載自己的類,可以使用Flight::path()方法來指定要從哪個目錄加載類。

基本示例

假設我們有如下目錄樹:

# 示例路徑
/home/user/project/my-flight-project/
├── app
│   ├── cache
│   ├── config
│   ├── controllers - 包含此項目的控制器
│   ├── translations
│   ├── UTILS - 包含僅用於此應用程序的類(這是為了後面的示例故意全部大寫)
│   └── views
└── public
    └── css
    └── js
    └── index.php

您可能已經注意到,這與此文檔站點的文件結構相同。

您可以像這樣指定要從每個目錄加載:


/**
 * public/index.php
 */

// 添加一個路徑給自動加載程序
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

/**
 * app/controllers/MyController.php
 */

// 不需要命名空間

// 建議將所有自動加載的類命名為帕斯卡命名法(每個單詞首字母大寫,沒有空格)
// 截至 3.7.2 版本,您可以通過運行 Loader::setV2ClassLoading(false); 來使用帕斯卡_蛇_命名法命名您的類
class MyController {

    public function index() {
        // 做一些事情
    }
}

命名空間

如果您有命名空間,實際上實現這一點變得非常容易。您應該使用Flight::path()方法來指定應用程序的根目錄(而不是文檔根目錄或public/文件夾)。


/**
 * public/index.php
 */

// 添加一個路徑給自動加載程序
Flight::path(__DIR__.'/../');

現在您的控制器可能看起來像這樣。查看下面的示例,但請注意評注中的重要信息。

/**
 * app/controllers/MyController.php
 */

// 必須有命名空間
// 命名空間與目錄結構相同
// 命名空間必須遵循與目錄結構相同的大小寫
// 命名空間和目錄不能有任何下劃線(除非 Loader::setV2ClassLoading(false) 已設置)
namespace app\controllers;

// 建議將所有自動加載的類命名為帕斯卡命名法(每個單詞首字母大寫,沒有空格)
// 截至 3.7.2 版本,您可以通過運行 Loader::setV2ClassLoading(false); 來使用帕斯卡_蛇_命名法命名您的類
class MyController {

    public function index() {
        // 做一些事情
    }
}

如果您希望自動加載 utils 目錄中的類,則基本上可以執行相同的操作:


/**
 * app/UTILS/ArrayHelperUtil.php
 */

// 命名空間必須與目錄結構及大小寫相匹配(請注意 UTILS 目錄是全部大寫
//     如上面的文件樹所示)
namespace app\UTILS;

class ArrayHelperUtil {

    public function changeArrayCase(array $array) {
        // 做一些事情
    }
}

類名中的下劃線

截至 3.7.2 版本,您可以運行Loader::setV2ClassLoading(false);,來使用帕斯卡_蛇_命名法命名您的類。這將允許您在類名中使用下劃線。這不建議使用,但對於需要的用戶來說是可用的。


/**
 * public/index.php
 */

// 添加一個路徑給自動加載程序
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);

/**
 * app/controllers/My_Controller.php
 */

// 不需要命名空間

class My_Controller {

    public function index() {
        // 做一些事情
    }
}

Learn/troubleshooting

故障排除

这个页面将帮助您解决在使用Flight时可能遇到的常见问题。

常见问题

404未找到或意外路由行为

如果您看到404未找到错误(但您发誓它确实存在,而且没有拼写错误),实际上可能是因为您在路由终点返回一个值而不是仅仅输出它。这样做的原因是故意的,但可能会让一些开发人员感到意外。


Flight::route('/hello', function(){
    // 这可能导致404未找到错误
    return 'Hello World';
});

// 您可能想要的是
Flight::route('/hello', function(){
    echo 'Hello World';
});

造成这种情况的原因是路由器中内置的特殊机制,它将返回输出视为“继续下一个路由”。您可以在路由部分中查看这种行为的文档。

类未找到(自动加载不起作用)

可能有几个原因导致这种情况发生。以下是一些示例,但请确保您还查看了自动加载部分。

文件名不正确

最常见的情况是类名与文件名不匹配。

如果您有一个名为 MyClass 的类,那么文件应该命名为 MyClass.php。如果您有一个名为 MyClass 的类,而文件命名为 myclass.php,那么自动加载程序将找不到它。

命名空间不正确

如果您正在使用命名空间,那么命名空间应该与目录结构匹配。

// 代码

// 如果您的MyController位于app/controllers目录中并且具有命名空间
// 这样将无法工作。
Flight::route('/hello', 'MyController->hello');

// 您需要选择以下其中一种选项
Flight::route('/hello', 'app\controllers\MyController->hello');
// 或者如果您在顶部有一个use语句

use app\controllers\MyController;

Flight::route('/hello', [ MyController::class, 'hello' ]);
// 也可以写成
Flight::route('/hello', MyController::class.'->hello');
// 还有...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);

path() 未定义

在骨架应用程序中,这在 config.php 文件中定义,但为了使您的类能够被找到,您需要确保在尝试使用它之前定义了 path() 方法(可能是指向您的目录根)。


// 向自动加载器添加路径
Flight::path(__DIR__.'/../');

Install

安装

下载文件

如果您正在使用Composer,可以运行以下命令:

composer require flightphp/core

或者您可以直接下载文件 并将其提取到您的 web 目录中。

配置您的 Web 服务器

Apache

对于 Apache,请编辑您的 .htaccess 文件如下:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

注意: 如果您需要在子目录中使用 Flight,请在 RewriteEngine On 之后添加 RewriteBase /subdir/

注意: 如果您希望保护所有服务器文件,例如 db 或 env 文件, 请将以下内容放入您的 .htaccess 文件中:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

对于 Nginx,请将以下内容添加到您的服务器声明中:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

创建您的 index.php 文件

<?php

// 如果您正在使用 Composer,请要求自动加载程序。
require 'vendor/autoload.php';
// 如果您没有使用 Composer,请直接加载框架
// require 'flight/Flight.php';

// 然后定义一个路由并分配一个函数来处理请求。
Flight::route('/', function () {
  echo 'hello world!';
});

// 最后,启动框架。
Flight::start();

License

MIT 许可证

版权所有 © 2024 @mikecao, @n0nag0n

特此免费授予任何获得本软件副本及相关文档文件(以下简称“软件”)的人,无偿使用本软件的权限,包括但不限于使用、复制、修改、合并、发布、分发、再许可以及销售本软件的副本,并允许被授予本软件的人员这样做,但须符合以下条件:

上述版权通知和本许可通知应包含在所有副本或重要部分的软件中。

本软件按原样提供,不附带任何形式的担保,包括但不限于适销性、特定用途的适用性和非侵权性的保证。在任何情况下,作者或版权所有者均不承担任何索赔、损害赔偿或其他责任,无论是合同诉讼、侵权行为或其他方面,来源于、无论是源于还是与本软件或本软件的使用或其他交易有关。

About

什么是Flight?

Flight是一个快速、简单、可扩展的PHP框架。它非常多才多艺,可以用来构建任何类型的Web应用程序。它被设计得简单易懂,并且易于理解和使用。

Flight对于那些刚接触PHP并想学习如何构建Web应用程序的初学者来说是一个很棒的框架。对于希望更多控制其Web应用程序的经验丰富的开发者来说,它也是一个很好的框架。它被设计成轻松构建RESTful API、简单Web应用程序或复杂Web应用程序。

快速开始

<?php

// 如果使用composer安装
require 'vendor/autoload.php';
// 或者如果手动通过zip文件安装
// require 'flight/Flight.php';

Flight::route('/', function() {
  echo '你好,世界!';
});

Flight::route('/json', function() {
  Flight::json(['你好' => '世界']);
});

Flight::start();

已经足够简单了吧?在文档中了解更多关于Flight的信息!

骨架/样板应用

有一个示例应用可以帮助您开始使用Flight框架。前往 flightphp/skeleton 获取开始的说明!您还可以访问 examples 页面,以获取关于Flight可能实现的一些功能的灵感。

社区

我们在Matrix聊天,可以通过#flight-php-framework:matrix.org与我们交流。

贡献

有两种方法可以为Flight做出贡献:

  1. 您可以通过访问 core repository 来为核心框架做贡献。
  2. 您可以为文档做出贡献。这个文档网站托管在 Github 上。如果您发现错误或希望改进某些内容,请随时进行更正并提交pull request!我们努力跟上事务,但更新和语言翻译是受欢迎的。

要求

Flight需要PHP 7.4或更高版本。

注意: PHP 7.4得到支持,因为在撰写本文时(2024年),PHP 7.4是一些LTS Linux发行版的默认版本。迫使迁移至PHP >8会为这些用户带来很多困扰。该框架也支持PHP >8。

授权许可

Flight根据 MIT 许可发布。

Awesome-plugins/php_cookie

Cookies

overclokk/cookie 是一个简单的库,用于在应用程序中管理 cookie。

安装

使用 composer 安装很简单。

composer require overclokk/cookie

用法

使用方法就是在 Flight 类中注册一个新方法。


use Overclokk\Cookie\Cookie;

/*
 * 在您的引导文件或 public/index.php 文件中设置
 */

Flight::register('cookie', Cookie::class);

/**
 * ExampleController.php
 */

class ExampleController {
    public function login() {
        // 设置一个 cookie

        // 想要将其设置为 false,以便获得一个新的实例
        // 如果您想要自动完成,请使用下面的注释
        /** @var \Overclokk\Cookie\Cookie $cookie */
        $cookie = Flight::cookie(false);
        $cookie->set(
            'stay_logged_in', // cookie 的名称
            '1', // 您想设置的值
            86400, // cookie 应持续的秒数
            '/', // 可以访问到 cookie 的路径
            'example.com', // 可以访问到 cookie 的域
            true, // cookie 只会通过安全的 HTTPS 连接传输
            true // cookie 只能通过 HTTP 协议访问
        );

        // 可选地,如果您希望保留默认值,并且希望以更长时间设置 cookie
        $cookie->forever('stay_logged_in', '1');
    }

    public function home() {
        // 检查您是否拥有该 cookie
        if (Flight::cookie()->has('stay_logged_in')) {
            // 将用户放在例如仪表板区域。
            Flight::redirect('/dashboard');
        }
    }
}

Awesome-plugins/php_encryption

PHP 加密

defuse/php-encryption 是一个可用于加密和解密数据的库。着手开始加密和解密数据相当简单。他们有一个很棒的tutorial来帮助解释如何使用该库的基础知识,以及有关加密的重要安全影响。

安装

使用 composer 很容易进行安装。

composer require defuse/php-encryption

设置

然后,您需要生成一个加密密钥。

vendor/bin/generate-defuse-key

这将输出一个您需要妥善保管的密钥。您可以将密钥保存在您的app/config/config.php文件中的数组底部。虽然这不是完美的位置,但至少是一个选项。

用法

现在您拥有该库和一个加密密钥,您可以开始加密和解密数据。


use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;

/*
 * 在您的引导文件或 public/index.php 中设置
 */

// 加密方法
Flight::map('encrypt', function($原始数据) {
    $加密密钥 = /* $config['encryption_key'] 或者是存放密钥位置的 file_get_contents */;
    return Crypto::encrypt($原始数据, Key::loadFromAsciiSafeString($加密密钥));
});

// 解密方法
Flight::map('decrypt', function($加密数据) {
    $加密密钥 = /* $config['encryption_key'] 或者是存放密钥位置的 file_get_contents */;
    try {
        $原始数据 = Crypto::decrypt($加密数据, Key::loadFromAsciiSafeString($加密密钥));
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
        // 一种攻击!加载了错误的密钥,或者自创建以来,密文已更改--在数据库中已损坏或Eve试图执行攻击时故意修改。

        // ...以适合您的应用程序的方式处理这种情况...
    }
    return $原始数据;
});

Flight::route('/encrypt', function() {
    $加密数据 = Flight::encrypt('这是一个机密');
    echo $加密数据;
});

Flight::route('/decrypt', function() {
    $加密数据 = '...'; // 从某处获取加密数据
    $解密数据 = Flight::decrypt($加密数据);
    echo $解密数据;
});

Awesome-plugins/php_file_cache

Wruczek/PHP-File-Cache

輕巧,簡單且獨立的PHP文件緩存類

優勢

點擊這裡查看代碼。

安裝

通過 composer 安裝:

composer require wruczek/php-file-cache

用法

使用非常簡單。

use Wruczek\PhpFileCache\PhpFileCache;

$app = Flight::app();

// 將緩存存儲的目錄傳遞給構造函數
$app->register('cache', PhpFileCache::class, [ __DIR__ . '/../cache/' ], function(PhpFileCache $cache) {

    // 這可以確保僅在生產模式下使用緩存
    // ENVIRONMENT 是在您的啟動文件或應用程序的其他地方設置的常數
    $cache->setDevMode(ENVIRONMENT === 'development');
});

然后您可以像這樣在代碼中使用它:


// 獲取緩存實例
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
    return date("H:i:s"); // 返回要緩存的數據
}, 10); // 10 秒

// 或者
$data = $cache->retrieve('simple-cache-test');
if(empty($data)) {
    $data = date("H:i:s");
    $cache->store('simple-cache-test', $data, 10); // 10 秒
}

文檔

訪問https://github.com/Wruczek/PHP-File-Cache查看完整文檔,並確保您查看examples文件夾。

Awesome-plugins/permissions

FlightPHP/Permissions

这是一个权限模块,可以在您的项目中使用,如果您的应用程序中有多个角色,并且每个角色有稍微不同的功能。此模块允许您为每个角色定义权限,然后检查当前用户是否具有权限访问某个页面或执行某个操作。

单击此处查看 GitHub 上的存储库。

安装

运行 composer require flightphp/permissions,您就可以开始了!

用法

首先,您需要设置权限,然后告诉您的应用程序这些权限的含义。最终,您将使用 $Permissions->has()->can()is() 检查权限。has()can() 具有相同的功能,但命名不同,以使您的代码更易读。

基本示例

假设您的应用程序中有一个功能,用于检查用户是否已登录。您可以像这样创建一个权限对象:

// index.php
require 'vendor/autoload.php';

// 一些代码

// 然后您可能有一些内容告诉您当前角色是谁
// 可能您有一些内容从会话变量中提取当前角色
// 以定义此内容
// 在某人登录后,否则他们将拥有 'guest' 或 'public' 角色。
$current_role = 'admin';

// 设置权限
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
    return $current_role !== 'guest';
});

// 您可能希望在 Flight 中某处持久化此对象
Flight::set('permission', $permission);

然后在某个控制器中,您可能会有如下代码。

<?php

// 某个控制器
class SomeController {
    public function someAction() {
        $permission = Flight::get('permission');
        if ($permission->has('loggedIn')) {
            // 做某事
        } else {
            // 做其他事
        }
    }
}

您还可以使用此功能跟踪用户在应用程序中是否具有执行某些操作的权限。 例如,如果您的应用程序允许用户与软件上的帖子进行交互,您可以 检查他们是否有权限执行某些操作。

$current_role = 'admin';

// 设置权限
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
    if($current_role === 'admin') {
        $permissions = ['create', 'read', 'update', 'delete'];
    } else if($current_role === 'editor') {
        $permissions = ['create', 'read', 'update'];
    } else if($current_role === 'author') {
        $permissions = ['create', 'read'];
    } else if($current_role === 'contributor') {
        $permissions = ['create'];
    } else {
        $permissions = [];
    }
    return $permissions;
});
Flight::set('permission', $permission);

然后在某个控制器中...

class PostController {
    public function create() {
        $permission = Flight::get('permission');
        if ($permission->can('post.create')) {
            // 做某事
        } else {
            // 做其他事
        }
    }
}

注入依赖项

您可以将依赖项注入定义权限的闭包中。如果您有某种切换、ID 或任何其他要检查的数据点,这很有用。对于 Class->Method 类型的调用同样适用,只是您需要在方法中定义参数。

闭包

$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
    // ... 代码
});

// 在您的控制器文件中
public function createOrder() {
    $MyDependency = Flight::myDependency();
    $permission = Flight::get('permission');
    if ($permission->can('order.create', $MyDependency)) {
        // 做某事
    } else {
        // 做其他事
    }
}

namespace MyApp;

class Permissions {

    public function order(string $current_role, MyDependency $MyDependency = null) {
        // ... 代码
    }
}

使用类设置权限的快捷方式

您还可以使用类来定义您的权限。如果您有很多权限要保持代码整洁,这很有用。您可以这样做:

<?php

// 启动代码
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');

// myapp/Permissions.php
namespace MyApp;

class Permissions {

    public function order(string $current_role, int $user_id) {
        // 假设您先前设置好了这一点
        /** @var \flight\database\PdoWrapper $db */
        $db = Flight::db();
        $allowed_permissions = [ 'read' ]; // 每个人都可以查看订单
        if($current_role === 'manager') {
            $allowed_permissions[] = 'create'; // 管理员可以创建订单
        }
        $some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
        if($some_special_toggle_from_db) {
            $allowed_permissions[] = 'update'; // 如果用户有特殊开关,他们可以更新订单
        }
        if($current_role === 'admin') {
            $allowed_permissions[] = 'delete'; // 管理员可以删除订单
        }
        return $allowed_permissions;
    }
}

很酷的部分是,还有一个简便方法可以使用(也可以被缓存!),您只需告诉权限类将类中的所有方法映射到权限中。因此,如果您有一个名为 order() 和一个名为 company() 的方法,这些将自动映射,因此您只需运行 $Permissions->has('order.read')$Permissions->has('company.read') 即可正常工作。定义这些非常困难,所以请跟着我学习。您只需要执行以下操作:

创建要分组的权限类。

class MyPermissions {
    public function order(string $current_role, int $order_id = 0): array {
        // 决定权限的代码
        return $permissions_array;
    }

    public function company(string $current_role, int $company_id): array {
        // 决定权限的代码
        return $permissions_array;
    }
}

然后使用此库使权限可发现。

$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);

最后,在您的代码库中调用权限以检查用户是否被允许执行给定的权限。

class SomeController {
    public function createOrder() {
        if(Flight::get('permissions')->can('order.create') === false) {
            die('您无法创建订单。抱歉!');
        }
    }
}

缓存

要启用缓存,请参阅简单的 wruczak/phpfilecache 库。以下是启用此功能的示例。


// 此 $app 可以是您代码的一部分,
// 或者您可以只传递 null,并在构造函数中
// 从 Flight::app() 获取
$app = Flight::app();

// 现在它接受此文件缓存。将来可以轻松添加其他缓存。
$Cache = new Wruczek\PhpFileCache\PhpFileCache;

$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 是要将其缓存多少秒。如果不使用缓存,请勿包含此选项

然后开启吧!

Awesome-plugins/index

令人惊叹的插件

Flight非常可扩展。有许多插件可以用于向您的Flight应用程序添加功能。其中一些得到了Flight团队的官方支持,而其他一些是微型/轻量级库,可帮助您入门。

缓存

缓存是加速应用程序的绝佳方法。有许多缓存库可与Flight一起使用。

调试

在本地环境中进行开发时,调试至关重要。有一些插件可以提升您的调试体验。

数据库

数据库是大多数应用程序的核心。这是您存储和检索数据的方式。有些数据库库只是用来编写查询的包装器,而有些是完整的ORM。

会话

对于API来说,会话实际上并不那么有用,但对于构建Web应用程序来说,会话可以对保持状态和登录信息至关重要。

模板

模板是任何具有UI的Web应用程序的核心。有许多模板引擎可与Flight一起使用。

贡献

有插件想要分享吗?提交拉取请求将其添加到列表中!

Awesome-plugins/pdo_wrapper

PdoWrapper PDO 辅助类

Flight 自带一个用于 PDO 的辅助类。它允许您轻松地查询您的数据库,使用所有准备/执行/获取所有结果的功能。它极大简化了您查询数据库的方式。每一行结果都作为 Flight 集合类返回,允许您通过数组语法或对象语法访问您的数据。

注册 PDO 辅助类

// 注册 PDO 辅助类
Flight::register('db', \flight\database\PdoWrapper::class, ['mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
]);

用法

此对象扩展了 PDO,因此所有正常的 PDO 方法都可用。以下方法已添加以使查询数据库更容易:

runQuery(string $sql, array $params = []): PDOStatement

用于INSERTS、UPDATES,或者如果您打算在 while 循环中使用 SELECT

$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $something ]);
while($row = $statement->fetch()) {
    // ...
}

// 或写入数据库
$db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]);
$db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]);

fetchField(string $sql, array $params = []): mixed

从查询中获取第一个字段

$db = Flight::db();
$count = $db->fetchField("SELECT COUNT(*) FROM table WHERE something = ?", [ $something ]);

fetchRow(string $sql, array $params = []): array

从查询中获取一行

$db = Flight::db();
$row = $db->fetchRow("SELECT id, name FROM table WHERE id = ?", [ $id ]);
echo $row['name'];
// 或
echo $row->name;

fetchAll(string $sql, array $params = []): array

从查询中获取所有行

$db = Flight::db();
$rows = $db->fetchAll("SELECT id, name FROM table WHERE something = ?", [ $something ]);
foreach($rows as $row) {
    echo $row['name'];
    // 或
    echo $row->name;
}

注意 IN() 语法

这还有一个有用的 IN() 语句包装器。您可以简单地传递一个问号作为 IN() 的占位符,然后是一个值数组。以下是这种用法的示例:

$db = Flight::db();
$name = 'Bob';
$company_ids = [1,2,3,4,5];
$rows = $db->fetchAll("SELECT id, name FROM table WHERE name = ? AND company_id IN (?)", [ $name, $company_ids ]);

完整示例

// 示例路由及如何使用该包装器
Flight::route('/users', function () {
    // 获取所有用户
    $users = Flight::db()->fetchAll('SELECT * FROM users');

    // 流式传输所有用户
    $statement = Flight::db()->runQuery('SELECT * FROM users');
    while ($user = $statement->fetch()) {
        echo $user['name'];
        // or echo $user->name;
    }

    // 获取单个用户
    $user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);

    // 获取单个值
    $count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');

    // 特殊 IN() 语法以帮助你(确保 IN 大写)
    $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
    // 您也可以这样做
    $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [ '1,2,3,4,5']);

    // 插入新用户
    Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
    $insert_id = Flight::db()->lastInsertId();

    // 更新用户
    Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);

    // 删除用户
    Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);

    // 获取受影响行数
    $statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
    $affected_rows = $statement->rowCount();

});

Awesome-plugins/session

鬼/会话

PHP 会话管理器(非阻塞、闪存、分段、会话加密)。 使用 PHP open_ssl 可选择加密/解密会话数据。支持文件、MySQL、Redis 和 Memcached。

单击这里查看代码。

安装

使用composer安装。

composer require ghostff/session

基本配置

您不需要传入任何内容即可使用默认设置与您的会话。您可以在GitHub Readme中阅读更多设置。


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

// 有一件事要记住,那就是每次加载页面都必须提交您的会话
// 或者您需要在配置中运行 auto_commit。

简单示例

以下是您可能如何使用此程序的简单示例。

Flight::route('POST /login', function() {
    $session = Flight::session();

    // 在此执行登录逻辑
    // 验证密码等。

    // 如果登录成功
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // 每次写入会话时,必须有意识地提交它。
    $session->commit();
});

// 此检查可以在受限页面逻辑中,或用中间件包装后执行。
Flight::route('/一些受限页面', function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }

    // 在此执行受限页面逻辑
});

// 中间件版本
Flight::route('/一些受限页面', function() {
    // 常规页面逻辑
})->addMiddleware(function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }
});

更复杂的示例

以下是您可能如何使用此程序的更复杂示例。


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// 将自定义路径设置为会话配置文件,并为会话ID设置一个随机字符串
$app->register('session', Session::class, ['path/to/session_config.php', bin2hex(random_bytes(32))], function(Session $session) {
        // 或者您可以手动覆盖配置选项
        $session->updateConfiguration([
            // 如果您希望将会话数据存储在数据库中(如果您想要像“注销所有设备”功能之类的功能)
            Session::CONFIG_DRIVER        => Ghostff\Session\Drivers\MySql::class,
            Session::CONFIG_ENCRYPT_DATA  => true,
            Session::CONFIG_SALT_KEY      => hash('sha256', 'my-super-S3CR3T-salt'), // 请将其更改为其他内容
            Session::CONFIG_AUTO_COMMIT   => true, // 只在需要时才执行此操作,否则很难 commit() 您的会话。
                                                   // 另外,您可以执行 Flight::after('start', function() { Flight::session()->commit(); });
            Session::CONFIG_MYSQL_DS         => [
                'driver'    => 'mysql',             # 用于PDO dns的数据库驱动程序,例如(mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # 数据库主机
                'db_name'   => 'my_app_database',   # 数据库名称
                'db_table'  => 'sessions',          # 数据库表
                'db_user'   => 'root',              # 数据库用户名
                'db_pass'   => '',                  # 数据库密码
                'persistent_conn'=> false,          # 避免每次脚本需要与数据库交谈时建立新连接的开销,从而使web应用程序更快。找到自己的缺点
            ]
        ]);
    }
);

帮助!我的会话数据没有持久化!

您是否设置了会话数据,但在请求之间没有持续保留?您可能忘记提交会话数据。您可以在设置会话数据后调用 $session->commit() 完成此操作。

Flight::route('POST /login', function() {
    $session = Flight::session();

    // 在此执行登录逻辑
    // 验证密码等。

    // 如果登录成功
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // 每次写入会话时,必须有意识地提交它。
    $session->commit();
});

解决此问题的另一种方法是,在设置会话服务时,在您的配置中将 auto_commit 设置为 true。 这将在每次请求之后自动提交您的会话数据。


$app->register('session', Session::class, ['path/to/session_config.php', bin2hex(random_bytes(32))], function(Session $session) {
        $session->updateConfiguration([
            Session::CONFIG_AUTO_COMMIT   => true,
        ]);
    }
);

另外,您可以执行 Flight::after('start', function() { Flight::session()->commit(); }); 在每次请求之后提交会话数据。

文档

访问GitHub Readme获取完整文档。 如果您想自己查看这个包,那么默认_config.php 文件中的配置选项都[有很好的文档] (https://github.com/Ghostff/Session/blob/master/src/default_config.php)。 代码简单易懂。

Awesome-plugins/runway

跑道

跑道是一个CLI应用程序,可帮助您管理您的Flight应用程序。它可以生成控制器,显示所有路由等。它基于优秀的 adhocore/php-cli 库。

点击这里 查看代码。

安装

使用 composer 安装。

composer require flightphp/runway

基本配置

第一次运行跑道时,它将引导您完成设置过程并在项目根目录中创建一个 .runway.json 配置文件。此文件将包含一些Runway正常工作所需的配置。

用法

跑道有许多命令可用于管理您的Flight应用程序。有两种简单的方法可以使用跑道。

  1. 如果您使用的是骨架项目,可以从项目的根目录运行 php runway [command]
  2. 如果您是通过composer安装的包来使用跑道,您可以从项目的根目录运行 vendor/bin/runway [command]

对于任何命令,您都可以传入 --help 标志以获取有关如何使用命令的更多信息。

php runway routes --help

以下是一些示例:

生成控制器

根据您的 .runway.json 文件中的配置,默认位置将为您在 app/controllers/ 目录中生成一个控制器。

php runway make:controller MyController

生成活动记录模型

根据您的 .runway.json 文件中的配置,默认位置将为您在 app/records/ 目录中生成一个控制器。

php runway make:record users

例如,如果您有以下架构的 users 表:idnameemailcreated_atupdated_at,则类似以下内容的文件将在 app/records/UserRecord.php 文件中创建:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * users 表的ActiveRecord类。
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 * 
 * @property int $id
 * @property string $name
 * @property string $email
 * @property string $created_at
 * @property string $updated_at
 * // 一旦在 $relations 数组中定义了关系,您也可以在此处添加关系
 * @property CompanyRecord $company 关系示例
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations 为该模型设置关系
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [];

    /**
     * 构造函数
     * @param mixed $databaseConnection 数据库连接
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

显示所有路由

这将显示当前在Flight中注册的所有路由。

php runway routes

如果您只想查看特定路由,您可以传入一个标志以过滤路由。

# 仅显示 GET 路由
php runway routes --get

# 仅显示 POST 路由
php runway routes --post

# 等等

自定义跑道

如果您要为Flight创建一个包,或者想要将自定义命令添加到您的项目中,您可以通过为您的项目/包创建一个 src/commands/, flight/commands/, app/commands/, 或 commands/ 目录来实现此目的。

要创建一个命令,您只需扩展 AbstractBaseCommand 类,并至少实现一个 __construct 方法和一个 execute 方法。

<?php

declare(strict_types=1);

namespace flight\commands;

class ExampleCommand extends AbstractBaseCommand
{
    /**
     * 构造函数
     *
     * @param array<string,mixed> $config 来自 .runway-config.json 的JSON配置
     */
    public function __construct(array $config)
    {
        parent::__construct('make:example', '为文档创建示例', $config);
        $this->argument('<funny-gif>', '滑稽gif的名称');
    }

    /**
     * 执行函数
     *
     * @return void
     */
    public function execute(string $controller)
    {
        $io = $this->app()->io();

        $io->info('创建示例...');

        // 在这里执行操作

        $io->ok('示例已创建!');
    }
}

有关如何将自定义命令构建到您的Flight应用程序中的更多信息,请参阅 adhocore/php-cli 文档

Awesome-plugins/tracy_extensions

Tracy Flight Panel Extensions

=====

这是一组扩展,可以让与 Flight 的工作变得更加丰富。

这是面板

Flight Bar

每个面板都显示关于您的应用程序非常有帮助的信息!

Flight Data Flight Database Flight Request

单击这里查看代码。

安装

运行 composer require flightphp/tracy-extensions --dev,您就开始了!

配置

您需要做很少的配置才能启动此功能。在使用此功能之前,您需要初始化 Tracy 调试器https://tracy.nette.org/en/guide

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// 引导代码
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// 您可能需要使用 Debugger::enable(Debugger::DEVELOPMENT) 指定您的环境

// 如果您的应用程序中使用数据库连接,则有一个必需的 PDO 包装器,仅在开发中使用(请勿在生产环境中使用!)
// 它具有与常规 PDO 连接相同的参数
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// 或者如果您将这个附加到 Flight 框架
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// 现在,每当进行查询时,它将捕获时间、查询和参数

// 连接这些点
if(Debugger::$showBar === true) {
    // 这需为false,否则 Tracy 无法实际渲染 :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// 更多代码

Flight::start();

额外配置

会话数据

如果您有自定义会话处理程序(例如 ghostff/session),您可以将任何会话数据数组传递给 Tracy,它将自动为您输出。您可以在 TracyExtensionLoader 构造函数的第二个参数中的 session_data 键中传递它。


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

if(Debugger::$showBar === true) {
    // 这需为false,否则 Tracy 无法实际渲染 :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// 路由和其他事物...

Flight::start();

Latte

如果您在项目中安装了 Latte,您可以使用 Latte 面板来分析您的模板。您可以将 Latte 实例传递给 TracyExtensionLoader 构造函数的第二个参数中的 latte 键。



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', Engine::class, [], function($latte) {
    $latte->setTempDirectory(__DIR__ . '/temp');

    // 这是您向 Tracy 添加 Latte 面板的位置
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // 这需为false,否则 Tracy 无法实际渲染 :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

Awesome-plugins/tracy

Tracy

Tracy 是一个令人惊叹的错误处理程序,可以与 Flight 一起使用。它有许多面板可以帮助您调试应用程序。扩展和添加您自己的面板也非常容易。Flight 团队为 Flight 项目创建了一些特定的面板,使用了 flightphp/tracy-extensions 插件。

安装

使用 composer 进行安装。实际上,您希望在没有开发版本的情况下安装此项,因为 Tracy 自带一个生产错误处理组件。

composer require tracy/tracy

基本配置

有一些基本的配置选项可供开始使用。您可以在 Tracy 文档 中了解更多信息。


require 'vendor/autoload.php';

use Tracy\Debugger;

// Enable Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // 有时候您需要显式设置 (还有 Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // 您也可以提供一个 IP 地址数组

// 这里是错误和异常将被记录的地方。请确保此目录存在且可写。
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // 显示所有错误
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // 所有错误,除去已弃用的通知
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // 如果 Debugger 栏可见,则 Flight 无法设置 content-length

    // 这对于 Flight 的 Tracy 扩展是特定的,如果您已经包含了它
    // 否则请将其注释掉。
    new TracyExtensionLoader($app);
}

有用提示

当您调试代码时,有一些非常有用的函数可以为您输出数据。

Awesome-plugins/active_record

Flight 活动记录

活动记录是将数据库实体映射到 PHP 对象。简单地说,如果您在数据库中有一个名为 users 的表,您可以将该表中的一行“转换”为 User 类和代码库中的 $user 对象。查看基本示例

单击这里查看 GitHub 上的存储库。

基本示例

假设您有以下表:

CREATE TABLE users (
    id INTEGER PRIMARY KEY, 
    name TEXT, 
    password TEXT 
);

现在,您可以设置一个新类来表示这个表:

/**
 * 活动记录类通常是单数的
 * 
 * 强烈建议在此添加表的属性注释
 * 
 * @property int    $id
 * @property string $name
 * @property string $password
 */
class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        // 您可以这样设置
        parent::__construct($database_connection, 'users');
        // 或者这样设置
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

现在,让神奇发生吧!

// 适用于 sqlite
$database_connection = new PDO('sqlite:test.db'); // 仅作示例,您可能会使用真实的数据库连接

// 适用于 mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');

// 或者 mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// 或者基于非对象的 mysqli 创建
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');

$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// 或 $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// 这里不能使用 $user->save() 否则它会认为这是一个更新!

echo $user->id; // 2

添加新用户就是这么简单!现在数据库中有一个用户行,如何获取它呢?

$user->find(1); // 查找 id = 1 的用户并返回它。
echo $user->name; // 'Bobby Tables'

如果您想查找所有用户呢?

$users = $user->findAll();

有特定条件的情况呢?

$users = $user->like('name', '%mamma%')->findAll();

看起来多么有趣?让我们安装它并开始使用吧!

安装

只需使用 Composer 安装

composer require flightphp/active-record 

用法

这可以作为独立库使用,也可以与 Flight PHP 框架一起使用。完全取决于您。

独立使用

只需确保将 PDO 连接传递给构造函数。

$pdo_connection = new PDO('sqlite:test.db'); // 仅作示例,您可能会使用真实的数据库连接

$User = new User($pdo_connection);

不想总是在构造函数中设置数据库连接?请参阅数据库连接管理以获取其他想法!

作为 Flight 方法注册

如果您使用 Flight PHP 框架,您可以将 ActiveRecord 类注册为服务,但您其实不必这样做。

Flight::register('user', 'User', [ $pdo_connection ]);

// 然后您可以在控制器、函数等中这样使用

Flight::user()->find(1);

runway 方法

runway 是 Flight 的 CLI 工具,为此库提供了自定义命令。

# 用法
php runway make:record database_table_name [class_name]

# 示例
php runway make:record users

这将在 app/records/ 目录中创建一个名为 UserRecord.php 的新类,内容如下:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * users 表的活动记录类。
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 *
 * @property int $id
 * @property string $username
 * @property string $email
 * @property string $password_hash
 * @property string $created_dt
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations 设置模型的关系
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [
        // 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
    ];

    /**
     * 构造函数
     * @param mixed $databaseConnection 数据库连接
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

CRUD 函数

find($id = null) : boolean|ActiveRecord

查找记录并将其分配给当前对象。如果传递了某种 $id,它将使用该值在主键上执行查找。如果未传递任何内容,它将仅查找表中的第一条记录。

此外,您可以在查询表之前传递其他辅助方法给它。

// 在查找记录之前查找一些条件
$user->notNull('password')->orderBy('id DESC')->find();

// 根据特定 id 查找记录
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

查找指定表中的所有记录。

$user->findAll();

isHydrated(): boolean (v0.4.0)

如果当前记录已填充(从数据库中提取),则返回 true

$user->find(1);
// 如果找到一条记录并有数据...
$user->isHydrated(); // true

insert(): boolean|ActiveRecord

将当前记录插入数据库中。

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
基于文本的主键

如果有基于文本的主键(例如 UUID),您可以在插入之前以两种方式设置主键值。

$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // 或 $user->save();

或者可以通过事件自动生成主键值。

class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
        // 您还可以以此方式设置 primaryKey 而不是上面的数组。
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // 或者您可以根据需要生成唯一 id
    }
}

如果在插入之前没有设置主键,它将被设置为 rowid,并且数据库将为您生成它,但它不会持久保存,因为该字段可能不存在于您的表中。这就是为什么建议使用事件自动处理这一点的原因。

update(): boolean|ActiveRecord

更新数据库中的当前记录。

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();

save(): boolean|ActiveRecord

将当前记录插入或更新到数据库中。如果记录具有 id,则会执行更新,否则将执行插入。

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

注意: 如果在类中定义了关系,且如果已定义、实例化并具有需要更新的数据,则它还将递归保存那些关系。(v0.4.0 及更高版本)

delete(): boolean

从数据库中删除当前记录。

$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();

您还可以在执行搜索之前删除多条记录。

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

脏数据是指在记录中已更改的数据。

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// 这时候没有数据是“脏”的。

$user->email = 'test@example.com'; // 现在电子邮件被认为是“脏”的,因为它已更改。
$user->update();
// 现在没有数据是脏的,因为已更新并持久保存在数据库中。

$user->password = password_hash()'newpassword'); // 现在这是脏的
$user->dirty(); // 传递空将清除所有脏条目。
$user->update(); // 什么都不会更新,因为没有被标记为脏。

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 名字和密码都已更新。

copyFrom(array $data): ActiveRecord(v0.4.0)

这是 dirty() 方法的别名。这更清晰地表明您正在做什么。

$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 名字和密码都已更新。

isDirty(): boolean(v0.4.0)

如果当前记录已更改,则返回 true

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

将当前记录重置为初始状态。在循环类型行为中非常有用。如果传递 true,它还会重置用于查找当前对象的查询数据(默认行为)。

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);

foreach($users as $user) {
    $user_company->reset(); // 从干净的状态开始
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string(v0.4.1)

在运行 find()findAll()insert()update()save() 方法后,您可以获取构建的 SQL,并将其用于调试目的。

SQL 查询方法

select(string $field1 [, string $field2 ... ])

如果需要,您可以仅选择表中几列(在具有许多列的宽表上性能更好)。

$user->select('id', 'name')->find();

from(string $table)

您还可以选择其他表!为何不选呢?

$user->select('id', 'name')->from('user')->find();

join(string $table_name, string $join_condition)

您甚至可以加入到数据库中的另一张表。

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

您可以设置一些自定义的 where 参数(您不能在此 where 语句中设置参数)

$user->where('id=1 AND name="demo"')->find();

安全提示 - 您可能想要做类似 $user->where("id = '{$id}' AND name = '{$name}'")->find();。请不要这样做!这容易受到 SQL 注入攻击。有很多在线文章,请谷歌“sql 注入攻击 php”,您将会找到很多关于这个主题的文章。使用此库的正确方法是,而不是使用 where() 方法,您应该使用更像是 $user->eq('id', $id)->eq('name', $name)->find(); 的方法。如果确实需要这样做,PDO 库有 $pdo->quote($var) 可以为您转义。仅在使用 quote() 后才可以在 where() 语句中使用它。

group(string $group_by_statement)/groupBy(string $group_by_statement)

按特定条件对结果进行分组。

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

按特定方式排序返回的查询。

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

限制返回的记录数量。如果给定第二个整数,则将偏移,limit 就像在 SQL 中一样。

$user->orderBy('name DESC')->limit(0, 10)->findAll();

WHERE 条件

equal(string $field, mixed $value) / eq(string $field, mixed $value)

field = $value

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

field <> $value

$user->ne('id', 1)->find();

isNull(string $field)

field IS NULL

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

field IS NOT NULL

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

field > $value

$user->gt('id', 1)->find();

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

field < $value

$user->lt('id', 1)->find();

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

field >= $value

$user->ge('id', 1)->find();

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

field <= $value

$user->le('id', 1)->find();

like(string $field, mixed $value) / notLike(string $field, mixed $value)

field LIKE $valuefield NOT LIKE $value

$user->like('name', 'de')->find();

in(string $field, array $values) / notIn(string $field, array $values)

field IN($value)field NOT IN($value)

$user->in('id', [1, 2])->find();

between(string $field, array $values)

field BETWEEN $value AND $value1

$user->between('id', [1, 2])->find();

关系

使用此库,您可以设置几种类型的关系。您可以在表之间设置一对多和一对一关系。这需要事先在类中进行一些额外的设置。

设置 $relations 数组并不困难,但猜测正确的语法可能会令人困惑。


protected array $relations = [
    // 您可以使用任何您喜欢的键名。ActiveRecord 的名称可能很好。例如:user、contact、client
    'user' => [
        // 必需
        // self::HAS_MANY、self::HAS_ONE、self::BELONGS_TO
        self::HAS_ONE, // 这是关系的类型

        // 必需
        'Some_Class', // 这是此关系将引用的“其他” ActiveRecord 类

        // 必需
        // 根据关系类型
        // self::HAS_ONE = 引用连接的外键
        // self::HAS_MANY = 引用连接的外键
        // self::BELONGS_TO = 引用连接的本地键
        'local_or_foreign_key',
        // 顺便说一下,如果您想回顾或提出任何疑问,请告诉我。

Awesome-plugins/latte

拿铁

拿铁 是一个功能齐全的模板引擎,非常易于使用,比Twig或Smarty更接近于PHP语法。它也非常容易扩展,添加自己的过滤器和函数。

安装

使用composer安装。

composer require latte/latte

基本配置

有一些基本配置选项可供开始使用。您可以在Latte文档中阅读更多内容。


use Latte\Engine as LatteEngine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) {

    // 这是Latte将缓存模板以加快速度的位置
    // Latte的一个好处是,当您更改模板时,它会自动刷新您的缓存!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // 告诉Latte您的视图的根目录在哪里
    // $app->get('flight.views.path') 在config.php文件中设置
    //   您也可以这样做 `__DIR__ . '/../views/'`
    $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});

简单布局示例

这是一个布局文件的简单示例。这个文件将用于包装所有其他视图。

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="zh-CN">
    <head>
        <title>{$title ? $title . ' - '}我的应用</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <nav>
                <!-- 您的导航元素在这里 -->
            </nav>
        </header>
        <div id="content">
            <!-- 这就是魔法所在 -->
            {block content}{/block}
        </div>
        <div id="footer">
            &copy; 版权所有
        </div>
    </body>
</html>

现在我们有一个将在该内容块内呈现的文件:

<!-- app/views/home.latte -->
<!-- 这告诉Latte,此文件位于layout.latte文件内部 -->
{extends layout.latte}

<!-- 这是将在布局内的内容块中呈现的内容 -->
{block content}
    <h1>主页</h1>
    <p>欢迎来到我的应用!</p>
{/block}

然后当您要在函数或控制器中呈现此内容时,您可以这样做:

// 简单路由
Flight::route('/', function () {
    Flight::latte()->render('home.latte', [
        'title' => '主页'
    ]);
});

// 或者,如果您正在使用控制器
Flight::route('/', [HomeController::class, 'index']);

// HomeController.php
class HomeController
{
    public function index()
    {
        Flight::latte()->render('home.latte', [
            'title' => '主页'
        ]);
    }
}

查看Latte文档以获取有关如何充分利用Latte的更多信息!

Awesome-plugins/awesome_plugins

令人敬畏的插件

Flight 非常可扩展。有许多插件可用于为您的 Flight 应用程序添加功能。一些得到了 Flight 团队的官方支持,而另一些是微型/精简库,可帮助您入门。

身份验证/授权

身份验证和授权对于任何需要对访问进行控制的应用程序至关重要。

缓存

缓存是加速应用程序的好方法。有多个缓存库可与 Flight 一起使用。

CLI

CLI 应用程序是与您的应用程序交互的好方法。您可以使用它们生成控制器,显示所有路由等。

Cookies

Cookie 是在客户端存储小数据块的好方法。可以用于存储用户喜好、应用程序设置等。

调试

在本地环境开发时,调试至关重要。有一些插件可以提升您的调试体验。

数据库

数据库是大多数应用程序的核心。这是您存储和检索数据的方式。一些数据库库只是用来编写查询的包装器,而另一些是完整的对象关系映射(ORM)。

加密

加密对于存储敏感数据的任何应用程序至关重要。加密和解密数据并不是非常困难,但正确存储加密密钥可能会有些困难。最重要的是永远不要将加密密钥存储在公共目录中或将其提交到代码存储库中。

会话

对于构建 Web 应用程序而言,会话对于维护状态和登录信息至关重要,但对于 API 来说并不是特别有用。

模板

模板是任何带有用户界面的 Web 应用程序的核心。有许多模板引擎可与 Flight 一起使用。

贡献

有要分享的插件吗?提交拉取请求将其添加到列表中吧!

Media

媒体

我们尝试追踪我们能找到的关于Flight的各种类型的媒体。请查看下面的不同资源,以了解更多关于Flight的信息。

文章

视频

Examples

需要快速开始吗?

您有两个选项可以开始使用 Flight:

需要一些灵感吗?

虽然这些不是 Flight 团队官方赞助的,但这些可能会为您提供有关如何构建结合 Flight 的自己项目的想法!

想分享您自己的示例吗?

如果您有项目希望分享,请提交拉取请求以将其添加到此列表!

Install/install

安装

下载文件

确保您的系统上已安装PHP。如果没有,请单击这里获取有关如何为您的系统安装它的说明。

如果您使用Composer,可以运行以下命令:

composer require flightphp/core

或者您可以下载文件并将其直接提取到您的web目录中。

配置您的 Web 服务器

内置 PHP 开发服务器

这是迄今为止最简单的启动方式。您可以使用内置服务器来运行应用程序,甚至可以使用SQLite作为数据库(只要您的系统上安装了sqlite3)而无需进行太多设置!只需在安装了PHP后运行以下命令:

php -S localhost:8000

然后在浏览器中打开http://localhost:8000

如果您想将项目的文档根目录设置为不同的目录(例如:您的项目是~/myproject,但您的文档根目录是~/myproject/public/),则可以在进入~/myproject目录后运行以下命令:

php -S localhost:8000 -t public/

然后在浏览器中打开http://localhost:8000

Apache

确保Apache已经安装在您的系统上。如果没有,请搜索如何在您的系统上安装Apache。

对于Apache,请使用以下内容编辑您的.htaccess文件:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

注意:如果您需要在子目录中使用flight,请在RewriteEngine On之后添加一行RewriteBase /subdir/

注意:如果要保护所有服务器文件,例如数据库或env文件。请将以下内容放入您的.htaccess文件:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

确保Nginx已经安装在您的系统上。如果没有,请搜索如何在您的系统上安装Nginx。

对于Nginx,请将以下内容添加到您的服务器声明中:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

创建您的 index.php 文件

<?php

// If you're using Composer, require the autoloader.
require 'vendor/autoload.php';
// 如果您没有使用Composer,请直接加载框架
// require 'flight/Flight.php';

// 然后定义一个路由,并分配一个处理请求的函数。
Flight::route('/', function () {
  echo 'hello world!';
});

// 最后,启动框架。
Flight::start();

安装 PHP

如果您的系统上已安装php,请跳过这些说明并转到下载部分

当然!以下是在macOS、Windows 10/11、Ubuntu 和 Rocky Linux上安装PHP的说明。我还将包括有关如何安装不同版本的PHP的详细信息。

macOS

使用 Homebrew 安装 PHP

  1. 安装 Homebrew(如果尚未安装):

    • 打开终端并运行:
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. 安装 PHP

    • 安装最新版本:
      brew install php
    • 要安装特定版本,例如,PHP 8.1:
      brew tap shivammathur/php
      brew install shivammathur/php/php@8.1
  3. 在不同版本之间切换

    • 解除当前版本的链接并链接所需的版本:
      brew unlink php
      brew link --overwrite --force php@8.1
    • 验证已安装的版本:
      php -v

Windows 10/11

手动安装 PHP

  1. 下载 PHP

    • 访问PHP for Windows,下载最新版本或特定版本(例如,7.4、8.0)的非线程安全zip文件。
  2. 解压 PHP

    • 将下载的zip文件解压缩到C:\php目录。
  3. 将 PHP 添加到系统PATH

    • 转到系统属性 > 环境变量
    • 系统变量下,找到Path并单击编辑
    • 添加路径C:\php(或者您解压缩PHP的任何位置)。
    • 点击确定关闭所有窗口。
  4. 配置 PHP

    • php.ini-development复制到php.ini
    • 编辑php.ini以根据需要配置PHP(例如,设置extension_dir,启用扩展)。
  5. 验证 PHP 安装

    • 打开命令提示符并运行:
      php -v

安装多个 PHP 版本

  1. 对于每个版本,重复上述步骤,将每个版本放在单独的目录中(例如,C:\php7C:\php8)。

  2. 通过调整系统PATH变量指向所需版本目录来在不同版本之间切换。

Ubuntu(20.04、22.04等)

使用apt安装 PHP

  1. 更新软件包列表

    • 打开终端并运行:
      sudo apt update
  2. 安装 PHP

    • 安装最新的PHP版本:
      sudo apt install php
    • 要安装特定版本,例如,PHP 8.1:
      sudo apt install php8.1
  3. 安装额外模块(可选):

    • 例如,安装MySQL支持:
      sudo apt install php8.1-mysql
  4. 在PHP版本之间切换

    • 使用update-alternatives
      sudo update-alternatives --set php /usr/bin/php8.1
  5. 验证已安装的版本

    • 运行:
      php -v

Rocky Linux

使用yum/dnf安装 PHP

  1. 启用 EPEL repository

    • 打开终端并运行:
      sudo dnf install epel-release
  2. 安装 Remi's repository

    • 运行:
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. 安装 PHP

    • 要安装默认版本:
      sudo dnf install php
    • 要安装特定版本,例如,PHP 7.4:
      sudo dnf module install php:remi-7.4
  4. 在PHP版本之间切换

    • 使用dnf模块命令:
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. 验证已安装的版本

    • 运行:
      php -v

一般说明