Ground Sunlight

Windowsで作る - Webプログラミングの開発環境(PHP)

ユーザ用ツール

サイト用ツール


サイドバー

メインメニュー

道具箱

リポジトリ編

フレームワーク編

Webサービス編

自然言語処理環境編

メタ
リンク


このページへのアクセス
今日: 1 / 昨日: 6
総計: 231

apricot:app:top

Apricot アプリ作成の準備

y2sunlight 2020-05-06

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')}} &copy; 2020 y2sunlight</footer>
    {!! DebugBar::render() !!}
</body>
</html>
  • <head>
    • <meta name='viewport'><title> を設定します
    • bootstrap、jquery をインクルードします
    • apricot用のCSSとJsのインクルードします( mian.cssmain.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=[])
    1. $view : テンプレート名
      上例では assets/views/stub.blade.php がテンプレートファイルになります
    2. $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テンプレートの titlecontent セクションを生成しています。
  • @if, @foreach はPHPの if, foreach と同じ機能を有するディレクティブです。

HTMLテンプレートに関しては本編BladeOneのリンクを項を参照して下さい。


テスト実行

ここまでの実装で一度実行してみましょう。ブラウザ上で以下のURLにアクセスしてみて下さい。

http://localhost/ws2019/apricot/public/

次の画面が表示されます

■ [menu1],[menu2],[menu3]を押すとそれぞれのスタブ画面が表示されます。
■ [About Me]を押すと y2sunlight.com が開きます。


コメント

コメントを入力. Wiki文法が有効です:
 
apricot/app/top.txt · 最終更新: 2020/06/03 13:11 by tanaka