====== Apricot アプリ作成の準備 ====== --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-05-06// [[apricot:top|Apricot に戻る]] 関連記事 * [[apricot:configuration|Apricot プロジェクトの作成]] * [[apricot:public|Apricot 公開フォルダ]] * [[apricot:core:top|Apricot コア]] * Apricot アプリ * Apricot アプリ作成の準備 * [[apricot:app:home|Apricot ホーム画面]] * [[apricot:app:error|Apricot エラー画面]] * [[apricot:app:db-model|Apricot データベースとモデル]] * [[apricot:app:user-list|Apricot ユーザ一覧画面]] * [[apricot:app:user-edit|Apricot ユーザ登録画面]] * [[apricot:app:validation|Apricot バリデーション]] * [[apricot:app:transaction|Apricot トランザクション]] * [[apricot:ext:middleware|Apricot 拡張]] まずは、apricotのアプリを作る為に以下を準備します。 * アプリフォルダ、名前空間、オートローディングの設定 * アプリ構築用のヘルパークラスとクラスエイリアスの設定 * HTMLレイアウトとベースコントローラの作成 ---- ===== フォルダの作成 ===== ==== appフォルダ ==== 以下に示すようにプロジェクトフォルダ下に、アプリ用のフォルダ app を作成し、その下に7つのフォルダ(Controllers, Exceptions, Foundation, Helpers, Middleware, Models, Services)を作成します。また、Controllersの下にはInterceptorsフォルダを作成します。 apricot [プロジェクト] | ├── app [アプリ] | | | ├── Controllers [コントローラ] | | | | | └── Interceptors [インターセプター] | | | ├── Exceptions [例外] | ├── Foundation [基盤] | ├── Helpers [ヘルパー] | ├── Middleware [ルミドルウェア] | ├── Models [モデル] | └── Services [サービス] (予約) * app フォルダはPSR-4によるオートローディングのベースフォルダとなります。従って、app 下のフォルダ及びファイルはPSR-4の命名規則に従う必要があり、クラスを保存するフォルダ及びファイルはUpperCamelCaseを使用して下さい。 \\ ==== オートローディングの変更 ==== 以下のようにcomposer.jsonを編集します。 {{fa>folder-open-o}} ** /apricot ** { "autoload" : { "psr-4" : { "App\\" : "app/", "Core\\" : "core/" }, } * ''autoload.psr-4'' の中に App名前空間へのマッピングを追加 オートローディングファイルを更新する為に、プロジェクトフォルダで以下のコマンドを実行します。 composer dump-autoload \\ ===== ヘルパークラス ===== アプリ用のヘルパー関数を作る準備として 表示用のHelperクラスを作成しておきます。必要に応じて、関数やクラスを追加していきます。 * ViewHelper --- 表示用(主にHTMLテンプレートで使用) {{fa>folder-open-o}} ** /apricot/app/Helpers ** \\ ===== クラスエイリアス ===== 完全修飾クラス名を短くコーディングする為に、クラスのエイリアスを作成します。このエイリアスは、特にHTMLテンプレートでの使用を想定しています。 以下に示す初期設定ファイル(aliases.setup.php)を config/setup フォルダの下に作成して下さい。 {{fa>folder-open-o}} ** /apricot/config/setup ** \Core\Input::class, 'QueryString' => \Core\QueryString::class, 'Session' => \Core\Session::class, 'Flash' => \Core\Flash::class, 'Cookie' => \Core\Cookie::class, 'Config' => \Core\Config::class, 'Log' => \Core\Log::class, 'Debug' => \Core\Debug::class, 'DebugBar' => \Core\DebugBar::class, 'ErrorBag' => \Core\Foundation\ErrorBag::class, /* App */ 'ViewHelper' => \App\Helpers\ViewHelper::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 }; * よく使うコアクラスとアプリ用のヘルパークラスのエイリアスを作っています 上で作った aliases.setup.php をアプリケーションの設定ファイル(app.php)に追加します。 {{fa>folder-open-o}} ** /apricot/config** [ config_dir('setup/whoops.setup.php'), /* Error handler(whoops) */ config_dir('setup/bladeone.setup.php'), /* View template (BladeOne) */ config_dir('setup/aliases.setup.php'), /* Class aliases for view template and so on */ ], 'middleware' =>[], 'auth' =>[], 'csrf' =>[], ]; \\ ===== 言語テキスト ===== アプリの作成過程で使用する共通の言語テキストを準備します。個別の画面については、その都度に追加して行きます。 {{fa>folder-open-o}} ** /apricot/assets/lang/ja ** [ 'title'=>env('APP_NAME'), 'menu'=>[ 'menu1'=>'menu1', 'menu2'=>'Menu2', 'menu3'=>'Menu3', 'logout'=>'Logout', 'about_me'=>'About Me', ], ], 'success' => [ 'db' => [ 'insert' => 'データを登録しました', 'update' => 'データを更新しました', 'delete' => 'データを削除しました', ], ], 'error'=>[ 'unknown'=>'エラーが発生しました', 'db' => [ 'access' => 'データアクセスが失敗しました', 'insert' => 'データの登録が失敗しました', 'update' => 'データの更新が失敗しました', 'delete' => 'データの削除が失敗しました', 'optimisstic_lock'=>'データが他のユーザによって更新されています', ], ], ]; * app --- アプリケーションのタイトルやメニュー * success --- 処理が成功した場合のメッセージ * error --- エラー用のメッセージ \\ ===== 例外クラス ===== まず最初に、アプリで発生する例外を様々な例外クラスのベースとなる ApplicationException を Exceptionクラス から派生させて作ります。 {{fa>folder-open-o}} ** /apricot/app/Exceptions ** user_message = isset($user_message) ? $user_message : __('messages.error.unknown'); parent::__construct($internal_message, $code, $previous); } /** * Get user error message * @return string */ public function getUserMessage() { return $this->user_message; } } * コンストラクタの引数のエラーメッセージが、ユーザ表示用( $user_message )と内部用( $internal_message )の2つがあります。 * ユーザ表示用のメッセージは、''getUserMessage()'' メソッドで取得できます。 * コンストラクタ内で使用されているボイラープレート ''%%__%%()'' はトランスレータ [[apricot:core:basic-class#langクラス|Core\Lang::get()]] を呼び出しています。トランスレータへの引数 'messages.error.unknown' は言語テキストのキーです(ドット表記)。 次に、ApplicationException から継承した楽観的ロック例外クラス OptimissticLockException を作ります。 {{fa>folder-open-o}} ** /apricot/app/Exceptions ** * コンストラクタ内で使用されているボイラープレート ''%%__%%()'' はトランスレータ [[apricot:core:basic-class#langクラス|Core\Lang::get()]] を呼び出しています。トランスレータへの引数 'messages.error.db.optimisstic_lock' は言語テキストのキーです(ドット表記)。 このようにして、アプリで発生する例外は ApplicationException から継承して作るようにします。 \\ ===== HTMLレイアウト ===== apricotで採用しているテンプレートエンジン [[basic-library:bladeone:3.37|BladeOne]] の文法に従ってアプリ全体のレイアウトを作ります。また、cssとJavaScriptファイルも準備します。 * layout.blade.php --- アプリ全体のレイアウト * main.css --- アプリ共通のスタイルシート * main.js --- アプリ共通で使用するJavaScript ==== layout.blade.php ==== アプリ全体で使用するHTMLテンプレートを示します。このテンプレートではbootstrapとjqueryを使用し、レスポンシブデザインを採用しています。 * [[https://getbootstrap.com/|bootstrap]] 4.3.1 * [[https://jquery.com/|jquery]] 3.3.1 {{fa>folder-open-o}} ** /apricot/assets/view ** {{__('messages.app.title')}} {!! DebugBar::renderHead() !!} @stack('scripts')

@yield('title')

@yield('content') {{-- error --}} @if($errors->count())
@foreach($errors as $key=>$value) {{$value}}
@endforeach
@endif {{-- msg --}} @if(Flash::has('msg'))
{{Flash::get('msg')}}
@endif
{{env('APP_NAME')}} © 2020 y2sunlight
{!! DebugBar::render() !!}
* **** * '''' と '''' を設定します * bootstrap、jquery をインクルードします * apricot用のCSSとJsのインクルードします( ''mian.css''、''main.js'' ) * [[apricot:core:basic-class#デバッグバー|DebugBar]]のHTMLヘッダーをレンダリングします * ''@stack('scripts')'' を使って、個々の画面用のJavaSctiptを差し込みます * **<body>** * ''<nav>'' にメニューバーを表示します * ''<main>''にコンテンツを表示します * ''@yield('title')'' を使って、個々の画面のタイトルを生成します * ''@yield('content')'' を使って、個々の画面のコンテンツを生成します * ''@if'', ''@foreach'' はPHPの if, foreach と同じ機能を持つ制御ディレクティブです * テンプレード変数 ''$errors'' が空でない場合はエラー表示します * フラッシュ変数 ''msg'' があるときはメッセージを表示します * 最後尾に [[apricot:core:basic-class#デバッグバー|DebugBar]]のHTMLボディをレンダリングします * **ボイラープレート** * ''__()'' はトランスレータ [[apricot:core:basic-class#langクラス|Core\Lang::get()]] を呼び出しています。トランスレータへの引数は言語テキスト(assests/lang/ja/messages.php)のキーです(ドット表記)。 * ''url_ver()'' はHTMLリソースをバージョニング対応する[[apricot:core:application-class#applicationクラスのヘルパー関数|Applicationクラスのヘルパー関数]]です。\\ (バージョニングとは、静的なHTMLリソースの更新をブラウザに通知する為に、静的ファイルの後ろに ''?v={version no}'' などのクエリ文字列を追加する事を指します) * ''route()'' はルートパスをURLに変換する[[apricot:core:application-class#applicationクラスのヘルパー関数|Applicationクラスのヘルパー関数]]です。 * ''user()'' はログインユーザのエンティティを呼び出します(後述のユーザ認証で説明)。 * ''env()'' は環境設定を取得する[[apricot:core:top#ヘルパー|ヘルパー関数]]です。 >''<nav>'' の中の ''@if(app('auth.menu',false))'' の部分は後述のユーザ認証で説明します。この段階ではコメントアウトされています。 HTMLテンプレートに関しては本編[[http://www.y2sunlight.com/ground/doku.php?id=basic-library:bladeone:3.37|BladeOne]]のリンクを項を参照して下さい。 \\ ==== main.css ==== Apricotで使用するCSSを以下に示します。このCSSは public/css 下に配置します。 {{fa>folder-open-o}} ** /apricot/public/css ** <code css main.css> @charset "UTF-8"; .form-control::-webkit-input-placeholder{color: #c6c6c6;} /* Google Chrome, Safari, Opera 15+, Android, iOS */ .form-control:-ms-input-placeholder, /* IE 10+ */ .form-control::-ms-input-placeholder{color: #c6c6c6;} .form-control::placeholder{color: #c6c6c6;} /* default #6c757d; */ .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { margin: 0.25em 0 1em 0; } .h1, h1 { font-size: 1.75rem } .h2, h2 { font-size: 1.5rem } .h3, h3 { font-size: 1.25rem } .h4, h4 { font-size: 1.0rem } .h5, h5 { font-size: 0.75rem } .h6, h6 { font-size: 0.5rem } .container { padding: 0px 3px; !important; overflow-x: hidden !important; /* x方向の余白を削除 */ } </code> \\ ==== main.js ==== 現バージョンのApricotではJavascriptを使っていませんが、カスタマイズ時には使用すると思われるので空のファイルを作っておきます。このJavascriptは public/js 下に配置します。 {{fa>folder-open-o}} ** /apricot/public/js ** <code js main.js> // It's empty now. </code> \\ ===== コントローラベース ===== コアクラスにはコントローラのベースクラスとして BaseController がありますが、これを直接継承するのではなく、アプリ用にもう一つコントローラのベースクラスを作ります。現段階でこのクラスは何もしていませんが、後でここに[[apricot:app:transaction|トランザクション]]用のコードを追加します。 {{fa>folder-open-o}} ** /apricot/app/Foundation ** <code php Controller.php> <?php namespace App\Foundation; use Core\Foundation\BaseController; /** * コントローラ */ class Controller extends BaseController { } </code> \\ ===== スタブ画面 ===== アプリの準備として最後にスタブ画面を作り、ルーティングからコントローラアクションの起動までをテストしてみます。 ==== ルーティング ==== 以下のように、config/routes.php にスタブコントローラのルートを追加して下さい。 {{fa>folder-open-o}} ** /apricot/config ** <code php routes.php> <?php //------------------------------------------------------------------- // Route Definition Callback //------------------------------------------------------------------- return function (FastRoute\RouteCollector $r) { $base = Core\Application::getInstance()->getRouteBase(); $r->addGroup($base, function (FastRoute\RouteCollector $r) use($base) { // Home // TODO: Stub Version $r->get('/', function() use($base){ header("Location: " . $base.'/stub'); }); // Stub $r->get('/stub[/{no:\d+}]', 'StubController@index'); }); }; </code> ルーティング設定に関しては[[https://github.com/nikic/FastRoute|FastRoute]]を参照して下さい。 \\ ==== コントローラ ==== 上述のアプリ用のコントローラベースを継承してスタブコントローラを作ります。 {{fa>folder-open-o}} ** /apricot/app/Controllers ** <code php StubController.php> <?php namespace App\Controllers; use App\Foundation\Controller; /** * Stubコントローラ */ class StubController extends Controller { /** * Stub Page * @return \Core\Foundation\Response */ public function index(int $no=null) { $title = "Stub {$no}"; $messages = []; return render('stub',['title'=>$title,'messages'=>$messages]); } } </code> * スタブコントローラにindexアクションを実装してる例です * ボイラープレートのrender()関数を呼び出してレスポンスをレンダリングしています * **render(string $view=null, array $variables=[])** - $view : テンプレート名\\ 上例では assets/views/stub.blade.php がテンプレートファイルになります - $variables : テンプレート変数の連想配列\\ 上例では [$title,$message] をテンプレートに渡しています。 \\ ==== HTMLテンプレート ==== 以下にスタブコントローラのindexアクションでレンダリングしているHTMLテンプレートを示します。 {{fa>folder-open-o}} ** /apricot/assets/views ** <code php stub.blade.php> {{-- 親レイアウト --}} @extends('layout') {{-- 追加スクリプト --}} @push('scripts') @endpush {{-- タイトル --}} @section('title', $title) {{-- コンテンツ --}} @section('content') @if(!empty($messages)) @foreach($messages as $message) <p>{{$message}}</p> @endforeach @endif @endsection </code> * ''extends''ディレクティブで layout.blade.php を継承しています。 * ''push''ディレクティブで layoutテンプレートの ''scripts''スタック にコードを追加しています。\\ (ここでは書くべきJavascriptがないので何も追加していません) * ''section''で layoutテンプレートの ''title'' と ''content'' セクションを生成しています。 * ''@if'', ''@foreach'' はPHPの if, foreach と同じ機能を有するディレクティブです。 HTMLテンプレートに関しては本編[[http://www.y2sunlight.com/ground/doku.php?id=basic-library:bladeone:3.37|BladeOne]]のリンクを項を参照して下さい。 \\ ==== テスト実行 ==== ここまでの実装で一度実行してみましょう。ブラウザ上で以下のURLにアクセスしてみて下さい。 <code> http://localhost/ws2019/apricot/public/ </code> 次の画面が表示されます [{{apricot:app:app01.png?nolink}}] ■ [menu1],[menu2],[menu3]を押すとそれぞれのスタブ画面が表示されます。\\ ■ [About Me]を押すと [[http://y2sunlight.com/ground/doku.php?id=apricot:top|y2sunlight.com]] が開きます。 \\