メインメニュー
XAMPP アレンジ
IED
WSL2
-
道具箱
リポジトリ編
フレームワーク編
公開ソフトウェア
メタ
リンク
- PHP ライブラリ
- PHP 言語
apricot:app:top目次
Apricot アプリ作成の準備
— y2sunlight 2020-05-06
関連記事
- Apricot アプリ
- 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を編集します。
/apricot
- composer.json
{ "autoload" : { "psr-4" : { "App\\" : "app/", "Core\\" : "core/" }, }
autoload.psr-4
の中に App名前空間へのマッピングを追加
オートローディングファイルを更新する為に、プロジェクトフォルダで以下のコマンドを実行します。
composer dump-autoload
ヘルパークラス
アプリ用のヘルパー関数を作る準備として 表示用のHelperクラスを作成しておきます。必要に応じて、関数やクラスを追加していきます。
- ViewHelper — 表示用(主にHTMLテンプレートで使用)
/apricot/app/Helpers
- ViewHelper.php
<?php namespace App\Helpers; class ViewHelper { /** * 日時のフォーマット * @param string $datetime * @param string $format * @return string */ static function formatDatetime(string $datetime, string $format='Y-m-d'):string { return date($format, strtotime($datetime)); } }
クラスエイリアス
完全修飾クラス名を短くコーディングする為に、クラスのエイリアスを作成します。このエイリアスは、特にHTMLテンプレートでの使用を想定しています。
以下に示す初期設定ファイル(aliases.setup.php)を config/setup フォルダの下に作成して下さい。
/apricot/config/setup
- aliases.setup.php
<?php //------------------------------------------------------------------- // ビューテンプレートで使うクラスエイリアスを登録 //------------------------------------------------------------------- return function():bool { $aliases = [ /* Core */ 'Input' => \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)に追加します。
/apricot/config
- app.php
<?php return [ 'setup' =>[ 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' =>[], ];
言語テキスト
アプリの作成過程で使用する共通の言語テキストを準備します。個別の画面については、その都度に追加して行きます。
/apricot/assets/lang/ja
- messages.php
<?php return [ 'app'=>[ '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クラス から派生させて作ります。
/apricot/app/Exceptions
- ApplicationException.php
<?php namespace App\Exceptions; use Exception; /** * アプリケーション例外 */ class ApplicationException extends Exception { /** * User error message * @var string */ private $user_message; /** * Create ApplicationException * @param string $user_message * @param string $internal_message * @param int $code * @param \Throwable $previous */ public function __construct(string $user_message=null, $internal_message=__CLASS__, int $code = 0, \Throwable $previous = null) { $this->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()
メソッドで取得できます。 - コンストラクタ内で使用されているボイラープレート
__()
はトランスレータ Core\Lang::get() を呼び出しています。トランスレータへの引数 'messages.error.unknown' は言語テキストのキーです(ドット表記)。
次に、ApplicationException から継承した楽観的ロック例外クラス OptimissticLockException を作ります。
/apricot/app/Exceptions
- OptimissticLockException.php
<?php namespace App\Exceptions; /** * 楽観的ロック例外 */ class OptimissticLockException extends ApplicationException { /** * Create Optimisstic Lock * @param string $message * @param \Exception $previous */ public function __construct() { parent::__construct(__('messages.error.db.optimisstic_lock'), __CLASS__); } }
- コンストラクタ内で使用されているボイラープレート
__()
はトランスレータ Core\Lang::get() を呼び出しています。トランスレータへの引数 'messages.error.db.optimisstic_lock' は言語テキストのキーです(ドット表記)。
このようにして、アプリで発生する例外は ApplicationException から継承して作るようにします。
HTMLレイアウト
apricotで採用しているテンプレートエンジン BladeOne の文法に従ってアプリ全体のレイアウトを作ります。また、cssとJavaScriptファイルも準備します。
- layout.blade.php — アプリ全体のレイアウト
- main.css — アプリ共通のスタイルシート
- main.js — アプリ共通で使用するJavaScript
layout.blade.php
アプリ全体で使用するHTMLテンプレートを示します。このテンプレートではbootstrapとjqueryを使用し、レスポンシブデザインを採用しています。
/apricot/assets/view
- layout.blade.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{__('messages.app.title')}}</title> <!-- stylesheet --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <link href="{{url_ver('css/main.css')}}" rel="stylesheet"> <!-- Scripts --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="{{url_ver('js/main.js')}}"></script> {!! DebugBar::renderHead() !!} @stack('scripts') </head> <body> <nav class="navbar navbar-expand-md navbar-dark bg-dark"> <a class="navbar-brand" href="{{route()}}">{{__('messages.app.title')}}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item"><a class="nav-link" href="{{route('stub/1')}}">{{__('messages.app.menu.menu1')}}</a></li> <li class="nav-item"><a class="nav-link" href="{{route('stub/2')}}">{{__('messages.app.menu.menu2')}}</a></li> <li class="nav-item"><a class="nav-link" href="{{route('stub/3')}}">{{__('messages.app.menu.menu3')}}</a></li> <li class="nav-item"><a class="nav-link" href="http://y2sunlight.com/ground/doku.php?id=apricot:top" target="_blank">{{__('messages.app.menu.about_me')}}</a></li> </ul> {{-- @if(app('auth.menu',false)) <ul class="navbar-nav ml-auto"> <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre> {{AuthUser::getUser()->account}} <span class="caret"></span> </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="{{route('logout')}}">{{__('messages.app.menu.logout')}}</a> </div> </ul> @endif --}} </div> </nav> <main class="mt-3 mb-5"> <div class="container"> <div class="row justify-content-center"> <div class="col-md-12"> <h1>@yield('title')</h1> @yield('content') {{-- error --}} @if($errors->count()) <div class="alert alert-danger mt-3"> @foreach($errors as $key=>$value) {{$value}}<br> @endforeach </div> @endif {{-- msg --}} @if(Flash::has('msg')) <div class="alert alert-info mt-3"> {{Flash::get('msg')}} </div> @endif </div> </div> </div> </main> <footer class="fixed-bottom bg-secondary text-center text-light py-1">{{env('APP_NAME')}} © 2020 y2sunlight</footer> {!! DebugBar::render() !!} </body> </html>
- <head>
<meta name='viewport'>
と<title>
を設定します- bootstrap、jquery をインクルードします
- apricot用のCSSとJsのインクルードします(
mian.css
、main.js
) - DebugBarのHTMLヘッダーをレンダリングします
@stack('scripts')
を使って、個々の画面用のJavaSctiptを差し込みます
- <body>
<nav>
にメニューバーを表示します<main>
にコンテンツを表示します@yield('title')
を使って、個々の画面のタイトルを生成します@yield('content')
を使って、個々の画面のコンテンツを生成します@if
,@foreach
はPHPの if, foreach と同じ機能を持つ制御ディレクティブです- テンプレード変数
$errors
が空でない場合はエラー表示します - フラッシュ変数
msg
があるときはメッセージを表示します
最後尾に DebugBarのHTMLボディをレンダリングします- ボイラープレート
__()
はトランスレータ Core\Lang::get() を呼び出しています。トランスレータへの引数は言語テキスト(assests/lang/ja/messages.php)のキーです(ドット表記)。url_ver()
はHTMLリソースをバージョニング対応するApplicationクラスのヘルパー関数です。
(バージョニングとは、静的なHTMLリソースの更新をブラウザに通知する為に、静的ファイルの後ろに?v={version no}
などのクエリ文字列を追加する事を指します)route()
はルートパスをURLに変換するApplicationクラスのヘルパー関数です。user()
はログインユーザのエンティティを呼び出します(後述のユーザ認証で説明)。env()
は環境設定を取得するヘルパー関数です。
<nav>
の中の@if(app('auth.menu',false))
の部分は後述のユーザ認証で説明します。この段階ではコメントアウトされています。HTMLテンプレートに関しては本編BladeOneのリンクを項を参照して下さい。
main.css
Apricotで使用するCSSを以下に示します。このCSSは public/css 下に配置します。
/apricot/public/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方向の余白を削除 */ }
main.js
現バージョンのApricotではJavascriptを使っていませんが、カスタマイズ時には使用すると思われるので空のファイルを作っておきます。このJavascriptは public/js 下に配置します。
/apricot/public/js
- main.js
// It's empty now.
コントローラベース
コアクラスにはコントローラのベースクラスとして BaseController がありますが、これを直接継承するのではなく、アプリ用にもう一つコントローラのベースクラスを作ります。現段階でこのクラスは何もしていませんが、後でここにトランザクション用のコードを追加します。
/apricot/app/Foundation
- Controller.php
<?php namespace App\Foundation; use Core\Foundation\BaseController; /** * コントローラ */ class Controller extends BaseController { }
スタブ画面
アプリの準備として最後にスタブ画面を作り、ルーティングからコントローラアクションの起動までをテストしてみます。
ルーティング
以下のように、config/routes.php にスタブコントローラのルートを追加して下さい。
/apricot/config
- 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'); }); };
ルーティング設定に関してはFastRouteを参照して下さい。
コントローラ
上述のアプリ用のコントローラベースを継承してスタブコントローラを作ります。
/apricot/app/Controllers
- 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]); } }
- スタブコントローラにindexアクションを実装してる例です
- ボイラープレートのrender()関数を呼び出してレスポンスをレンダリングしています
- render(string $view=null, array $variables=[])
- $view : テンプレート名
上例では assets/views/stub.blade.php がテンプレートファイルになります - $variables : テンプレート変数の連想配列
上例では [$title,$message] をテンプレートに渡しています。
HTMLテンプレート
以下にスタブコントローラのindexアクションでレンダリングしているHTMLテンプレートを示します。
/apricot/assets/views
- 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
extends
ディレクティブで layout.blade.php を継承しています。push
ディレクティブで layoutテンプレートのscripts
スタック にコードを追加しています。
(ここでは書くべきJavascriptがないので何も追加していません)section
で layoutテンプレートのtitle
とcontent
セクションを生成しています。@if
,@foreach
はPHPの if, foreach と同じ機能を有するディレクティブです。
HTMLテンプレートに関しては本編BladeOneのリンクを項を参照して下さい。
テスト実行
ここまでの実装で一度実行してみましょう。ブラウザ上で以下のURLにアクセスしてみて下さい。
http://localhost/ws2019/apricot/public/
次の画面が表示されます
■ [menu1],[menu2],[menu3]を押すとそれぞれのスタブ画面が表示されます。
■ [About Me]を押すと y2sunlight.com が開きます。
apricot/app/top.txt · 最終更新: 2020/06/03 13:11 by tanaka
コメント