$app_name = env('APP_NAME');
$log_name = config('monolog.name');
''env()'' は環境変数の取得を、''config()'' はドット表記で指定された構成変数の値を取得するボイラープレートです。ボイラープレートについては、このドキュメントの[[apricot:usage:ja:utility#ボイラープレート|ユーティリティー]]の章を参照して下さい。
以下は、ログ出力を行うシングルトンの例です。
Log::error($message);
Logクラスは、[[https://github.com/Seldaek/monolog|monolog]]のLoggerクラスをラップしたシングルトンで、''Log::error()'' はエラーレベルのログを出力するメソッドです。このコーディングは Laravelではお馴染みのファザードと同じスタイルをしています。Apricotのシングルトンは全てこのような静的アクセススタイルでメソッドを利用できるようになっています。
この他にもリクエストを取得する、Input、Session、Cookie、またはテンプレートエンジンのViewなどApricotでは様々なシングルトンが存在します。こららのシングルトンは、アプリケーションのどこからでも使用することができます。
このように、Apricotでは、ボイラープレートとシングルトンを使用した素朴で簡単なスタイルでコーディングができるようになっています。
\\
===== Apricotの概要 =====
Apricotの概要を知る為に、クライアントのリクエストを受け取ってからApricotがレスポンスを返すまでを、ユーザ登録アプリケーションを例に説明します。
\\
==== リクエストルータ ====
リクエストルータは、アプリケーションのエンドポイントがどの ''コントローラ@アクション'' を実行するのかを決定します。Apricotでは、リクエストルータに [[https://github.com/nikic/FastRoute|FastRoute]]を使用しています。ルーティングの設定は、''config/routes.php'' ファイルを編集することで行います。
以下は、ユーザ登録ページの CRUD操作(create, read, update, and delete)に対するルーティングの例です。
getRouteBase();
// Creates a route group with a common prefix.
$r->addGroup($base, function (FastRoute\RouteCollector $r) use($base)
{
// User
$r->get ('/users', 'UserController@index');
$r->get ('/user/create', 'UserController@create');
$r->post('/user/insert', 'UserController@insert');
$r->get ('/user/{id:\d+}/edit', 'UserController@edit');
$r->post('/user/{id:\d+}/update', 'UserController@update');
$r->post('/user/{id:\d+}/delete', 'UserController@delete');
});
};
\\
==== ミドルウェア ====
ミドルウェアは、ルーティングによって決定されたコントローラの前後でリクエストを処理し、必要に応じてレスポンスを生成することができます。ミドルウェアがレスポンスを生成した場合、コントローラのリクエスト処理は行われません。以下は、セッションを利用してユーザ認証を行うミドルウェア(セッション認証)の例です。
exclude))
{
return $next->invoke();
}
// Verifys whether user is authenticated.
if (AuthUser::verify())
{
return $next->invoke();
}
// Redirect to login page If not authenticated.
return redirect(route('login'));
}
}
アプリ部分に実装されるミドルウェアの全ての具象クラスは、process() メソッドを実装する必要があります。
ミドルウェアは全てのコントローラに介入しますが、$exclude変数に登録されているコントローラは除外します。''AuthUser'' は認証ユーザクラスのシングルトンで、''AuthUser::verify()'' はユーザ認証をチェックするメソッドです。ユーザ認証に成功すると次のインポーカー( $next ) にミドルウェアまたはコントローラ@アクションをインボークさせ、失敗した場合は、ログインページ( route('login') )にリダイレクトします。
Apricotでは、以下のミドルウェアがアプリとして app/Middleware 内に初期実装されています。
* ユーザ認証(基本認証 及び セッション認証)
* アクセスログ
* CSRF対策
これらは必要に応じて直接カスタマイズできます。また、アプリケーションの設定ファイル config/app.php を編集することにより新しいミドルウェアを追加することができます。
[
\App\Middleware\AccessLog::class, /* Access log */
\App\Middleware\VerifyCsrfToken::class, /* Verify CSRF Token */
\App\Middleware\Auth\SessionAuth::class, /* Session authentication */
\App\Middleware\InputConverter::class, /* Input Converter */
\App\Middleware\MyMiddleware::class, /* New MyMiddleware */
],
];
\\
==== コントローラ ====
ルーティングによって設定されているエンドポイントがアクセスされ、ミドルウェアによってレスポンスが生成されなかった場合、Apricotはコントローラ@アクションをインボークします。コントローラの全ての具象クラスはApricotのアプリ部分の ''App\Foundation\Controller'' を継承し、このControllersクラスは、コア部分の ''Apricot\Foundation\BaseController'' を継承しています。コントローラはプロジェクトディレクトリー下の ''app/Controllers'' に配置する必要があります( 名前空間は''\App\Controllers'' です )。
コントローラの継承関係
ConcreteController => Controller => BaseController
BaseController クラスの主な機能はコントローラーに[[#インターセプター]]を登録する事です。また、Controller クラスにはアクションに[[#トランザクション]]をサポートさせる機能があります。
以下はユーザコントローラの例です。ここには、コンストラクタと空のアクションメソッドを示します。
user = $user;
// Registers interceptors.
$this->intercept('insert', 'UserInterceptor@insert');
$this->intercept('update', 'UserInterceptor@update');
// Registers transactional actions.
$this->transactional('insert','update','delete');
}
public function index(){} /* Users list page. */
public function create(){} /* User registration page. */
public function insert(){} /* Inserts a user record. */
public function edit(int $id){} /* User edit page. */
public function update(int $id){} /* Updates a user record. */
public function delete(int $id){} /* Deletes a user record. */
}
コントローラのコンストラクタは、Auto Wiring 機能をサポートします。これは、コンストラクター引数の型ヒントを調べることにより、オブジェクトとそのすべての依存関係を再帰的に自動的に解決する機能です。但し、注入できるのはオブジェクト型の変数だけです。Auto Wiring には外部ライブラリーの [[https://github.com/thephpleague/container|League/Container]] を使用しています。
Auto Wiring 機能で使用する引数には、一般的にモデルクラスやサービスクラスを指定します。ここでは、ユーザモデルをコンストラクター引数に指定し、それをメンバ変数に格納しています。
また、上のコンストラクタではコントローラのアクションに[[#インターセプター]]を登録したり、アクションを[[#トランザクション|トランザクション化]]していますが、それらについては、以下の項を参照して下さい。
\\
==== HTTP リクエストの取得 ====
通常のフレームワークでは、CGIやPHP環境からアプリケーションに到達したすべてのデータ($_SERVER、$_GET、$_POST、$_FILES、$_SESSIONなどの変数)を1つのリクエストクラスにカプセル化します。リクエストクラスでは、これらのデータに加え、フラッシュと呼ばれる、一度だけ保存されるセッション変数(次の画面の遷移のときまで保存される変数)もサポートされています。
Apricotにはリクエストクラスがありません。その代わりに、個々の変数をカプセル化したクラスのシングルトンを持っています。リクエスト取得用に以下のシングルトンがあります:
* Input --- フォーム送信データ(GET変数またはPOST変数:methodに依存)
* QueryString --- クエリ文字列(GET変数)
* Session --- SESSION変数
* Flash --- 1回限り有効なSESSION変数
* Cookie --- COOKIE変数
これらはシングルトンなので、アプリケーションのどこからでもリクエストを取得することができます。以下は、ユーザコントローラのinsertアクションでリクエストを取得している例です。
/**
* Inserts a user record.
*
* @return \Apricot\Foundation\Response
*/
public function insert()
{
$inputs = Input::all();
// Do something.
// ...
}
''Input'' はフォームから送信データ(入力変数)を保持するクラスのシングルトンで、''Input::all()'' は全ての入力変数を取得するメソッドです(このコーディングは Laravelではお馴染みのファザードと同じスタイルをしています)。Apricotのシングルトンは全てこのような静的アクセススタイルでメソッドを利用できるようになっています。
\\
==== HTTP レスポンスの生成 ====
以下は、ユーザコントローラの編集フォームを表示するアクションの例です。
/**
* User edit page.
*
* @return \Apricot\Foundation\Response
*/
public function edit(int $id)
{
// Finds By the primary key
$user = $this->user->findOne($id);
if ($user!==false)
{
return render("user.edit", ["user"=>$user]);
}
else
{
return redirect(route("users"))->withOldErrors();
}
}
URIに含まれるユーザID( $id )からユーザモデル(
// Registers interceptors.
$this->intercept('insert', 'UserInterceptor@insert');
$this->intercept('update', 'UserInterceptor@update');
''
この例では、バリデーション後に、''Input::remove()'' を使用して、バリデーションだけで使用する入力変数を削除しています。完全なインターセプターの例は、次の項を参照して下さい。
\\
==== バリデーション ====
Apricotでは サーバ側のバリデーションに[[https://github.com/vlucas/valitron|Valitron]] を使用しています。以下に、前項で示したインターセプター( UserInterceptor@insert )の完全な例を示します。
/**
* Interceptor for insert method.
*
* @return void|\Apricot\Foundation\Response return Response if failed
*/
public function insert(Controller $controller)
{
$inputs = Input::all();
// Validation
$v =(new \Valitron\Validator($inputs))
->rule('required', ['account','password'])
->labels(inputLabels('messages.user.create'));
if(!$v->validate())
{
$errorBag = new ValidatorErrorBag($v->errors());
return redirect(back())->withInputs()->withErrors($errorBag);
}
// Removes unnecessary inputs
Input::remove('password_confirmation');
}
この例では、''Input::all()'' で入力変数を取得した後に、''Validator''のインスタンスを生成しています。
Validatorの ''rule()'' メソッドは入力変数に検証ルールを適用します。この例では、必須入力を意味する''required'' ルールを入力変数の account と password に適用しています。また、''labels()'' メソッドはエラーメッセージで使う項目名を設定しています。
バリデーションの実行は ''validate()'' メソッドで行います。バリデーションが失敗した時、''withInputs()'' で入力変数を、withErrors() でバリデーションのエラーバッグをフラッシュ変数に保存します。そして、redirect()で前画面にリダイレクトするレスポンスオブジェクトを生成し、それを返します。Apricotは、インターセプターがレスポンスオブジェクトを返した時、コントローラーアクションを呼び出さずに、そのレスポンスをクライアントに返します。
バリデーションの詳細は [[https://github.com/vlucas/valitron/blob/master/README.md|Valitron の README]] をご覧下さい。
\\
==== トランザクション ====
ユーザコントローラのコンストラクタには次のようなコードがあります。
// Registers transactional actions.
$this->transactional('insert','update','delete');
''$this->transactional() '' はアクションをトランザクション化するコントローラクラスのメソッドです。上の例では insert、update そして deleteアクションをトランザクション化しています。
Apricotは、アクションを呼び出す前にトランザクションを開始し、アクションが例外をスローしなかった場合、トランザクションが成功したものとみなして、そのトランザクションをコミットします。一方、アクションが ''ApplicationException'' 例外をスローした場合、Apricotは、それをキャッチしてエラーログを出力して、アクションに代わって前画面に戻るリダイレクトレスポンスを生成します。
\\
==== モデル ====
Apricotの個々のモデルクラスは、''\App\Foundation\Model'' から継承して作りますが、このクラスはカスタマイズされることを前提としています。初期に実装されている ''Model'' クラスは ORMに [[https://github.com/j4mie/idiorm|Idiorm]] を使用し、以下のメソッドを実装しています。
* findAll() --- 全件検索
* findOne(int $id) --- 主キー検索
* create(array $inputs=null) --- モデルの新規作成
* insert(array $inputs) --- レコードの挿入
* update($id, array $inputs) --- レコードの更新(楽観的ロック付き)
* delete($id) --- レコードの削除
これらの実装では、アプリケーションで使用する全てのテーブルが以下のカラムを持っていることが仮定されています。
* created_at (text) --- 作成日
* updated_at (text) --- 更新日
* version_no (integer) --- バージョンNo(楽観的ロックで使用)
このように、初期実装されている ''Model'' クラスには幾つかの前提があるので、アプリケーションに合わせてカスタマイズまたは再作成した方が良いかもしれません。また、ORMも [[https://github.com/j4mie/idiorm|Idiorm]] 以外のものを使うことができます。
以下は、初期実装されている ''Model'' クラスを継承したユーザモデルの例です。個々のモデルクラスは、''\App\Models'' の下に配置されます。
ユーザモデルでは、insert() と update() メソッドをオーバーラードしています。insert() では入力された ''password'' を暗号化してデータベースに保存しています。update() では入力された''password'' が入力されていない場合は、入力変数から除外し、そうでない場合は、暗号化してデータベースに保存しています。
\\
==== HTMLテンプレート ====
Apricotでは、テンプレートエンジンにLaravelと同じBladeを使用しています。実際に使用しているライブラリは [[https://github.com/EFTEC/BladeOne|BladeOne]] です。BladeOneは、Blade のスタンドアロンバージョンです。
以下は、ユーザコントロールの index アクションの例です。
public function index()
{
$users = $this->user->findAll();
return render("user.index", ["users"=>$users]);
}
ここでは、ボイラープレート''render()'' を使って、HTMLをレンダリングし、それをレスポンスとして返しています。render() の第1引数にはHTMLテンプレート名、第2引数にはテンプレート変数を[変数名 ⇒ 値]の形で渡します。
以下に、テンプレートの例( user.index )を示します。
{{-- Parent layout --}}
@extends('layout')
{{-- title --}}
@section('title', __('messages.user.index.title'))
{{--content --}}
@section('content')
{{__('messages.user.index.id')}}
{{__('messages.user.index.account')}}
{{__('messages.user.index.email')}}
{{__('messages.user.index.created_at')}}
@foreach($users as $user)
id}/edit")}}">
{{ $user->id }}
{{ $user->account }}
{{ $user->email }}
{{ ViewHelper::formatDatetime($user->created_at) }}
@endforeach
@endsection
テンプレートファイルは、プロジェクトディレクトリ下の ''assets/views/'' に配置します。一般的にテンプレートファイルは、''assets/views/{path}/{name}.blade.php'' として保存します。''{path}'' はサブディレクトリ下に配置したい場合のオプションですが、コントローラ名に関連した名前付けが一般的でしょう。上例のテンプレートファイルは、''assets/views/user/index.blade.php'' に配置しているので、テンプレート名は ''user.index'' になります。
上の例で見られる、@extends、@section 及び @endsection、@foreach 及び @endforeach は Blade のディレクティブです。また、''{{ $name }} '' は $name を表示するための Blade の構文です。詳しくは以下のドキュメントを参照して下さい。
* https://github.com/EFTEC/BladeOne --- BladeOne
* https://readouble.com/laravel/5.8/ja/blade.html --- laravel5.8のblade
''__ ( $key )'' はトランスレータを呼び出すApricotのボイラープレートです。詳しくは次項の[[#多言語化]]を参照して下さい。また、''ViewHelper'' はテンプレートヘルパーを実装するためのクラスで、''app/Helpers/'' の下に配置されています。''ViewHelper::formatDatetime()'' は日付を指定の書式で返すヘルパー関数です。
=== クラスアリアス ===
テンプレートヘルパーやシングルトン( Input, Session, Flashクラス など )はクラスアリアスが登録されているので、PHPのuse演算子や完全修飾した名前空間を使用することなく、テンプレートの中で使用できます。クラスアリアスの登録は ''config/setup/aliases.setup.php'' の中で以下のようにされています。
Apricot\Input::class,
'QueryString' => Apricot\QueryString::class,
'Session' => Apricot\Session::class,
'Flash' => Apricot\Flash::class,
'Cookie' => Apricot\Cookie::class,
'Config' => Apricot\Config::class,
'Log' => Apricot\Log::class,
'Debug' => Apricot\Debug::class,
'DebugBar' => Apricot\DebugBar::class,
'ErrorBag' => Apricot\Foundation\ErrorBag::class,
/* App */
'ViewHelper' => App\Helpers\ViewHelper::class,
'ValidatorErrorBag' => App\Foundation\ValidatorErrorBag::class,
'AuthUser' => App\Foundation\Security\AuthUser::class,
];
// Creates an alias for a class
foreach($aliases as $alias_name => $original_class)
{
class_alias($original_class, $alias_name);
}
return true; // Must return true on success
};
\\
==== 多言語化 ====
Apricotは多言語をサポートしています。''Lang'' シングルトンの ''Lang::get()'' メソッドはドット表記で指定されたキーから各言語用のメッセージを取得します。Apricotがどの言語を選択するかは、ブラウザで使用されている言語と準備されている言語ファイルによって自動的に決定されます。デフォルトの言語は、環境変数 ''APP_LANG'' で指定してます。この変数が指定されていない場合は英語になります。
$message = Lang::get('messages.user.index.account');
''Lang::get()'' メソッドはボイラープレートを使って次のようにも記述できます。
$message = __('messages.user.index.account');
各言語用のメッセージはプロジェクトディレクトリー下の ''assets/lang/{言語コード}/'' に配置された言語ファイルの中に保存され、そのファイル名がドット表記の最初のキーとなります。即ち、''messages'' で始まるキーを持つメッセージは 各言語毎に以下のファイルに保存されます。
your-project [プロジェクトディレクトリー]
|
├── assets
| |
| ├── lang
| | |
| | ├── ja
| | | ├── messages.php
| | |
| | ├── en
| | | ├── messages.php
以下に、messages.php の例を示します。
[
'index'=> [
'title'=>'ユーザ一覧',
'id'=>'ID',
'account'=>'アカウント',
'email'=>'メールアドレス',
'note'=>'備考',
'created_at'=>'登録日',
'btn_new'=>'新規',
],
],
];
\\