ngyuki/ritz

v0.0.2 2017-10-01 09:12 UTC

This package is auto-updated.

Last update: 2024-09-25 05:33:29 UTC


README

这是一个使用PSR-7(HTTP消息接口)和PSR-15(HTTP中间件)构建的轻量级、简洁的框架。

以下是一些所使用的库。

基本概念

在这个框架中,不使用所谓的控制器类继承。不存在如AbstractController之类的类作为应用程序中实现控制器的基类。

控制器的实例由DI容器管理,因此控制器需要的所有对象都是通过DI注入的。

如果需要在控制器前后使用公共的钩子点(例如,在AbstractController类的preDispatchpostDispatch中实现),可以通过在调度前放置PSR-15中间件来实现。

与许多通用框架的控制器相比,这个框架的控制器超轻量。因此,控制器的单元测试也变得容易。

示例应用程序

以下有示例应用程序。

以下是如何运行示例应用程序。

git clone https://github.com/ngyuki/php-ritz-app.git
cd php-ritz-app
composer install
php -S 0.0.0.0:8888 -t public/
open https://:8888/

目录结构

示例应用程序具有以下目录结构。

  • app/
    • 存储自动加载PHP类的目录
  • bootstrap/
    • 存储用于初始化DI容器的代码的目录
    • 存储不依赖于环境的初始化设置的目录
  • cache/
    • 写入缓存文件的目录
  • config/
    • 存储用于初始化DI容器的代码的目录
    • 存储环境依赖的应用程序设置文件的目录
  • public/
    • 设置服务器文档根的目录
  • resource/
    • 存储视图模板文件等的目录
  • tests/
    • 示例应用程序的测试代码

命名空间结构

示例应用程序的命名空间具有以下结构。

  • Ritz\App\Bootstrap
    • 存储用于应用程序初始化(如应用程序类或容器工厂)的类
  • Ritz\App\Component
    • 存储用于Controller或Service的其它组件类
  • Ritz\App\Controller
    • 存储应用程序的控制器类
  • Ritz\App\Middleware
    • 存储应用程序中使用的PSR-15中间件
  • Ritz\App\Service
    • 存储实现应用程序用例的服务类

### 処理の流れ

サンプルアプリは次の流れで処理されます。

- `ContainerFactory` がコンテナを作成する
    - `Configure` クラスが `bootstrap/` と `config/` から DI コンテナの初期化のコードを読む
    - `config/` からは環境変数 `APP_ENV` に基づいたファイルも読まれる
    - ルート定義も `bootstrap/` に含まれている
- コンテナから `Application` クラスのオブジェクトを取り出す
- `Application` オブジェクトを `Server::run` クラスに渡してアプリケーションを実行する
- `Application` が PSR-15 Middleware として実行される
    - 実行時にパイプラインを構築する
- パイプラインが順番に実行される

サンプルアプリの幾つかのコードには詳細なコメントが記述されているため、アプリケーション構成の参考にしてください。

## Application クラス

`Application` クラスは PSR-15 の `MiddlewareInterface` を実装し、`process` メソッドでパイプラインを初期化して実行する必要があります。

下記の 3 つのミドルウェアはフレームワークが提供する基本的なミドルウェアで、基本的には必須です(もちろん独自のミドルウェアに差し替えることも出来ます)。

- `RenderMiddleware`
- `RouteMiddleware`
- `DispatchMiddleware`

必要に応じて、アプリケーション独自のミドルウェアをパイプラインの任意の場所に差し込むことができます。例えば、次のようなアプリケーション独自パイプラインが考えられます。

- 例外発生時に特定のテンプレート選択する
- CSRF トークンのチェックを行う
- 認証のチェックとリダイレクトを行なう
- アクションの前後でリクエスト・レスポンスオブジェクトを加工する

## ルート定義

ルート定義は下記のようになります。ルーターの実装には `FastRoute` を使用しているため、具体的なルート定義の方法は `FastRoute` のドキュメントを参照してください。

```php
use function DI\value;
use FastRoute\RouteCollector;
use App\Controller\HomeController;
use App\Controller\UserController;

return [
    'app.routes' => value(function(RouteCollector $r) {
        $r->get('/', [HomeController::class, 'indexAction']);
        $r->get('/user/{id}', [UserController::class, 'showAction']);
    }),
];

路由处理器(get方法的第二个参数)指定由控制器名和动作名组成的数组。控制器名是从DI容器中获取控制器实例时的条目名称(通常是控制器的类名)。动作名是控制器的成员方法名。

如果路由处理器包含字符串索引,则将其直接设置为请求的属性。

// routes.php
return [
    'app.routes' => value(function(RouteCollector $r) {
        $r->get('/', [HomeController::class, 'indexAction', 'attr' => 'val']);
    }),
];

// HomeController.php
class HomeController
{
    public function attrAction(ServerRequestInterface $request)
    {
        $attr = $request->getAttribute('attr'); // val
    }
}

动作方法的参数

在控制器的动作方法中,根据参数的名称或类型提示,自动注入以下值或对象。

  • DI容器
  • 请求属性
  • PSR-15的process方法参数(ServerRequestInterfaceDelegateInterface
public function showAction(
    // DI コンテナから取得
    UserRepository $userRepository,
    // リクエストオブジェクト
    ServerRequestInterface $request,
    // リクエストのアトリビュートに設定されたルートパラメータ
    $id
) {
    return ['user' => $userRepository->get($id)];
}

请求属性包括所谓的路由参数,即路由定义中的/user/{id}之类的占位符参数。在上面的示例中,如果URL为/user/123,则根据路由参数将123设置到$id中。

如果其他中间件向请求属性中添加了任意值,则这些值也可以注入到方法参数中。

// ミドルウェアでリクエストに属性を追加
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    $requset = $requset->withAttribute('foo', 987);
    $requset = $requset->withAttribute(Bar::class, new Bar());
    return $delegate->process($request);
}

// アクションメソッドに注入される
public function showAction($foo, Bar $bar)) {
    // ...
}

动作方法的返回值

如果控制器动作方法返回ViewModel,则RendererMiddleware将进行模板渲染,并将结果反映在响应体中。

// ViewModel オブジェクトを返す
return new ViewModel();

// テンプレートにアサインする変数を指定する
return new ViewModel(['val' => 123]);

如果返回值是数组,则将其自动分配给分配的ViewModel

// 配列は自動的に ViewModel オブジェクトに変換される
return ['val' => 123];

渲染的模板名称是自动从控制器类名和动作方法名中导出的。例如,从App\Controller\HomeController::indexActionApp/Home/index,模板名称被导出。导出规则将在下面说明。

可以使用ViewModel指定模板名称,以使用不同的模板。

return (new ViewModel())->withTemplate('App/Home/index');

可以相对于自动导出的模板名称指定模板。例如,如果自动导出的模板名称是App/Home/index,则以下代码中的模板名称是App/Home/edit

return (new ViewModel())->withTemplate('./edit');

ViewModel实现PSR-15的ResponseInterface。当需要修改状态码或响应头时,可以使用ResponseInterface的方法。

// 200 以外のステータスコードとカスタムヘッダを指定する
return (new ViewModel())->withStatus(400)->withHeader('X-Custom', 'xxx');

如果动作方法返回实现ResponseInterfaceViewModel对象,则该响应对象将被直接发送到浏览器。

// リダイレクト
return new RedirectResponse('/');

如果动作方法返回字符串,则将其自动转换为TextResponse

// 文字列は TextResponse に変換される
return "hello";

// 上と等価
return new TextResponse("hello");

模板名称的自动解析

如果ViewModel中没有指定模板名称,则RenderMiddleware构造函数中传递的TemplateResolver将根据控制器类名和动作方法名导出模板名称。

TemplateResolver在构造函数中接收一个数组,该数组包含控制器命名空间和模板前缀的映射。例如,可以按照以下方式设置TemplateResolver实例。

return [
    'app.view.autoload' => [
        'Ritz\\App\\Controller\\' => 'App/',
    ],
    TemplateResolver::class => function (ContainerInterface $container) {
        return new TemplateResolver($container->get('app.view.autoload'));
    },
];

app.view.autoload类似于Composer的PSR-4自动加载器的设置。在上面的设置中,Ritz\\App\\Controller\\这样的命名空间前缀被替换为App/这样的模板名称。

此外,对类名和方法名执行以下处理,以导出模板名称。

  • 如果控制器类名后缀是Controller,则将其删除
  • 如果动作方法后缀是Action,则将其删除
  • 命名空间分隔符被替换为目录分隔符
  • 控制器名和方法名由目录分隔符连接

例如,对于类名和方法名Ritz\App\Controller\HomeController::indexAction,模板是App/Home/index