目次

Apricot アプリ作成の準備

y2sunlight 2020-05-06

Apricot に戻る

関連記事

まずは、apricotのアプリを作る為に以下を準備します。


フォルダの作成

appフォルダ

以下に示すようにプロジェクトフォルダ下に、アプリ用のフォルダ app を作成し、その下に7つのフォルダ(Controllers, Exceptions, Foundation, Helpers, Middleware, Models, Services)を作成します。また、Controllersの下にはInterceptorsフォルダを作成します。

apricot [プロジェクト]
 |
 ├── app [アプリ]
 |    |
 |    ├── Controllers [コントローラ]
 |    |    |
 |    |    └── Interceptors [インターセプター]
 |    |  
 |    ├── Exceptions [例外]
 |    ├── Foundation [基盤]
 |    ├── Helpers    [ヘルパー]
 |    ├── Middleware [ルミドルウェア]
 |    ├── Models     [モデル]
 |    └── Services   [サービス] (予約)


オートローディングの変更

以下のようにcomposer.jsonを編集します。

/apricot

composer.json
{
	"autoload" : {
		"psr-4" : {
			"App\\" : "app/",
			"Core\\" : "core/"
		},
}

オートローディングファイルを更新する為に、プロジェクトフォルダで以下のコマンドを実行します。

composer dump-autoload


ヘルパークラス

アプリ用のヘルパー関数を作る準備として 表示用のHelperクラスを作成しておきます。必要に応じて、関数やクラスを追加していきます。

/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'=>'データが他のユーザによって更新されています',
        ],
    ],
];


例外クラス

まず最初に、アプリで発生する例外を様々な例外クラスのベースとなる 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;
    }
}

次に、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__);
    }
}

このようにして、アプリで発生する例外は ApplicationException から継承して作るようにします。


HTMLレイアウト

apricotで採用しているテンプレートエンジン BladeOne の文法に従ってアプリ全体のレイアウトを作ります。また、cssと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>
<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]);
    }
}


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

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


テスト実行

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

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

次の画面が表示されます

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