例を使って実際の開発手順を記載していきます
以下の手順で作成していきます
お知らせ機能の要件・仕様は以下とします
内容 | |
---|---|
1. | システム管理画面にてお知らせの登録・編集・削除ができる |
2. | 登録できる内容は 1.日時(Y-m-d H 形式) 2.お知らせ内容 3.表示場所 4.表示・非表示の選択 とする |
3. | お知らせの表示場所は、 1.サイト管理者ダッシュボード 2.ユーザーマイページ 3.サイトトップ とする |
4. | 表示順は日時の降順(最新のものが先)とする |
5. | 表示件数は最新のものから最大5件とする |
6. | お知らせが登録されていない場合は「お知らせはありません」と表示する |
7. | データはソフトデリートとする |
$ php artisan make:migration --create=informations CreateInformationsTable
を実行し、マイグレーションファイルを作成します
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateInformationsTable extends Migration
{
protected $table = 'informations';
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create($this->table, function (Blueprint $table) {
$table->engine = 'InnoDB ROW_FORMAT=DYNAMIC';
$table->increments('id')->unsigned()->comment('一意の識別子');
$table->timestamp('datetime')->comment('日時');
$table->text('contents')->comment('お知らせ内容');
$table->smallinteger('place')->unsigned()->index()->comment('表示場所.1:サイト管理。2:マイページ。3:サイト');
$table->smallinteger('is_display')->unsigned()->index()->default(1)->comment('表示・非表示の選択.1:表示。2:非表示');
$table->softDeletesTz();
$table->timestampsTz();
});
DB::statement("ALTER TABLE `".$this->table."` COMMENT 'お知らせを管理するテーブル'");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists($this->table);
}
}
$ php artisan migrate
を実行し、テーブルを作成しますモデルクラスを作成します。まず
$ php artisan make:model Models\\Informations
を実行し、元となるモデルクラスを作成します。次に内容をテーブルに合わせて以下のように書き換えます
namespace App\Models;
use Azuki\App\Models\BaseSoftDeleteModel as Model;
class Informations extends Model
{
/**
* The database table account by the model.
*
* @var string
*/
protected $table = 'informations';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'datetime',
'contents',
'place',
'is_display',
];
/**
* function setCondition
*
* 検索条件を設定する
*
* @param QueryBuilder $query
* @param Array $condition
* @return QueryBuilder $query
*
*/
public function setCondition($query, $condition)
{
$query = parent::setCondition($query, $condition);
$query = $this->when($query, [$condition, 'id'], function($query, $value) {
return $query->where('id', '=', $value);
});
$query = $this->when($query, [$condition, 'place'], function($query, $value) {
return $query->whereIn('place', $value);
});
$query = $this->when($query, [$condition, 'is_display'], function($query, $value) {
return $query->whereIn('is_display', $value);
});
return $query;
}
}
継承元をuse Azuki\App\Models\BaseSoftDeleteModel as Model;
として
Azukiシステムのソフトデリートモデルの基礎クラスに変更します
※ハードデリートモデルの時は
BaseHardDeleteModel
を、
マスターデータを管理するモデルの場合はBaseMasterModel
を継承します
$table
$fillable
は一般的なLaravelモデル作成と同様に定義します
setCondition
メソッドはAzukiシステムで使用しており、検索のロジックを記述します。
上記ではID、place、is_displayでの検索を可能にしています。
コントローラ作成前に、コントローラーで使用するマスターデータの定義を作成します
config/azuki.master.php
を以下の内容で作成します。
return [
// お知らせ表示場所定義
'informationPlaceLabel' => [
'type' => 'file',
'list' => [
'1' => ['order' => '1', 'value'=> '1', 'name' => 'サイト管理'],
'2' => ['order' => '2', 'value'=> '2', 'name' => 'マイページ'],
'3' => ['order' => '3', 'value'=> '3', 'name' => 'サイト'],
],
],
// お知らせ表示・非表示定義
'informationDisplayLabel' => [
'type' => 'file',
'list' => [
'1' => ['order' => '1', 'value'=> '1', 'name' => '表示'],
'2' => ['order' => '2', 'value'=> '2', 'name' => '非表示'],
],
],
];
次にコントローラーファイルの作成をします
$ php artisan make:controller System\\InformationController
を実行し、コントローラーファイルを作成します。
namespace App\Http\Controllers\System;
use Azuki\App\Http\Controllers\System\BaseController;
use Azuki\App\Http\Modules\ActionTrait\BaseActionPackageWithDetail;
use Azuki\App\Http\Validation\SharedValidator as Validator;
use App\Models\Informations as Model;
class InformationController extends BaseController
{
/**
* サポートサービスのオブジェクト
*
* @var CtrlCupporter ctrlSupporter
*/
protected $ctrlSupporter;
/**
* 構成要素
*
* array $elements
*/
protected $elements = [
'datetime' => [
'form' => [
'title' => [
'size' => 3,
'type' => 'title',
'name' => '日時',
'required' => IS_REQUIRED,
],
'datetime' => [
'required' => IS_REQUIRED,
'size' => 8,
'type' => 'datetime',
'name' => 'datetime',
'column' => 'datetime',
'validate' => [
'rule' => 'required|date',
],
],
],
'list' => [
[
'type' => CONTROL_TYPE_DATETIME,
'width' => '12%',
'format' => 'Y年m月d日 H時',
'orderable' => true,
],
],
],
'contents' => [
'form' => [
'title' => [
'size' => 3,
'type' => 'title',
'name' => 'お知らせ内容',
'required' => IS_REQUIRED,
],
'contents' => [
'required' => IS_REQUIRED,
'size' => 8,
'type' => 'textarea',
'name' => 'contents',
'column' => 'contents',
'validate' => [
'rule' => 'required',
],
],
],
'list' => [
[
'type' => CONTROL_TYPE_TEXT,
'width' => '50%',
],
],
],
'place' => [
'form' => [
'title' => [
'size' => 3,
'type' => 'title',
'name' => '表示場所',
'required' => IS_REQUIRED,
],
'place' => [
'required' => IS_REQUIRED,
'size' => 8,
'type' => 'radio',
'name' => 'place',
'column' => 'place',
'select' => 'informationPlaceLabel',
'searchName' => 'search[place]',
'searchType' => 'checkbox',
'validate' => [
'rule' => 'required',
],
],
],
'list' => [
[
'type' => CONTROL_TYPE_SELECT,
'width' => '12%',
],
],
],
'is_display' => [
'form' => [
'title' => [
'size' => 3,
'type' => 'title',
'name' => '表示',
'required' => IS_REQUIRED,
],
'is_display' => [
'required' => IS_REQUIRED,
'size' => 8,
'type' => 'radio',
'name' => 'is_display',
'column' => 'is_display',
'default' => 1,
'select' => 'informationDisplayLabel',
'searchName' => 'search[is_display]',
'searchType' => 'checkbox',
'validate' => [
'rule' => 'required',
],
],
],
'list' => [
[
'type' => CONTROL_TYPE_BOOLEAN,
'width' => '8%',
'allowValue' => 1,
],
],
],
];
/**
* 画面構成に必要な要素のモジュールの定義
*
*/
protected $elementModues = [
\Azuki\App\Http\Modules\Forms\BasicElements::class,
];
/**
* 基本のアクションセットのトレイトを読み込みます
*/
use BaseActionPackageWithDetail;
/**
* ページのh1に表示する文字列
*
* string pageTitle
*/
protected $pageTitle = 'お知らせ管理';
/**
* 各画面タイプごとの要素のオーダー設定
*
* array $elementsOrder
*/
protected $elementsOrder = [
'search' => ['place', 'is_display' ],
'detail' => ['id', 'datetime', 'contents', 'place', 'is_display' ],
'form' => ['datetime', 'contents', 'place', 'is_display'],
'list' => ['id', 'datetime', 'contents', 'place', 'is_display', 'ctrl' ],
];
/**
* Create a new authentication controller instance.
*
* @return void
*/
public function __construct(Model $model, Validator $validator)
{
parent::__construct();
$this->setCtrlSuppoter([
'templatePrefix' => $this->viewPrefix.'templates.',
'model' => $model,
'validator' => $validator,
'order' => ['order' => 'datetime', 'condition' => ['datetime' => 'desc']],
// 共通のアサイン設定変数
'views' => [
'pageName' => 'お知らせ管理',
'controller' => 'information',
'search_shortcut' => false,
'needDatetimePicker' => true,
],
]);
}
}
いくつかのポイントを簡単に説明します
use Azuki\App\Http\Controllers\System\BaseController;
use Azuki\App\Http\Modules\ActionTrait\BaseActionPackageWithDetail;
use Azuki\App\Http\Validation\SharedValidator as Validator;
use App\Models\Informations as Model;
class InformationController extends BaseController
継承元をAzuki\App\Http\Controllers\System\BaseController
に変更します。
これにより、Azukiシステムの提供する基本的なコントローラーの機能が提供されます
ModelクラスとValidationクラスを使用できるようにuseします。
ここで使っているAzuki\App\Http\Validation\SharedValidator
は共通利用できるように作成した汎用のバリデーターです。
個別に作成した専用のバリデーターを使用することも可能です
Azuki\App\Http\Modules\ActionTrait\BaseActionPackageWithDetail
は各アクションを定義したトレイトです。
このトレイトはCtrlSupporterクラスを使用して各処理を実行しています。
この組み合わせを使うことで、コントローラーファイルには設定に関する定義を行ったり、任意の場所でのコールバックを登録する、
トレイトのメソッドをオーバーライドするといった部分的なコードの記述のみで管理画面を構築することが出来るようになります
/**
* サポートサービスのオブジェクト
*
* @var CtrlCupporter ctrlSupporter
*/
protected $ctrlSupporter;
/**
* 構成要素
*
* array $elements
*/
protected $elements = [
];
/**
* 画面構成に必要な要素のモジュールの定義
*
*/
protected $elementModues = [
\Azuki\App\Http\Modules\Forms\BasicElements::class,
];
/**
* 基本のアクションセットのトレイトを読み込みます
*/
use BaseActionPackageWithDetail;
/**
* ページのh1に表示する文字列
*
* string pageTitle
*/
protected $pageTitle = 'お知らせ管理';
/**
* 各画面タイプごとの要素のオーダー設定
*
* array $elementsOrder
*/
protected $elementsOrder = [
'search' => ['place', 'is_display' ],
'detail' => ['id', 'datetime', 'contents', 'place', 'is_display' ],
'form' => ['datetime', 'contents', 'place', 'is_display'],
'list' => ['id', 'datetime', 'contents', 'place', 'is_display', 'ctrl' ],
];
上記メンバ変数、ならびにトレイトの読み込みは必須です。
$elements
にて、画面構成要素の定義を行います。この変数の設定に応じて検索フォーム、一覧表示、詳細表示、編集画面
の全ての表示要素のレイアウト、表示内容、フォームの型、placeHolderといった様々な内容が構築されます。
ここで指定できる詳細については別途記載します。
$elementModues
は$elements
に追加する構成要素を定義しているクラス配列です。
一般に利用する、idや作成日時、一覧の詳細・編集・削除ボタンが\Azuki\App\Http\Modules\Forms\BasicElements
に定義されており、この配列に指定していることで利用可能になります。
他にも一般的に利用するようなパーツを別クラスで定義して読み込むことが可能です
$elementsOrder
で、各場所において使う要素を定義します。ここで定義された要素がそれぞれの場所で$elements
の定義に従って構成されることになります。
なお、$elementsOrder['form']
は必須です。新規作成・編集画面がない場合空の配列で定義しておく必要があります。
public function __construct(Model $model, Validator $validator)
{
parent::__construct();
$this->setCtrlSuppoter([
'templatePrefix' => $this->viewPrefix.'templates.',
'model' => $model,
'validator' => $validator,
'order' => ['order' => 'datetime', 'condition' => ['datetime' => 'desc']],
// 共通のアサイン設定変数
'views' => [
'pageName' => 'お知らせ管理',
'controller' => 'information',
'search_shortcut' => false,
'needDatetimePicker' => true,
],
]);
}
コンストラクタはほぼ固定で上述の通りになりますが、ここでCtrlSupporter
への初期設定登録をしており、
その設定を変更することで挙動をコントロールすることができます。
'templatePrefix' => $this->viewPrefix.'templates.'
は使用するテンプレートのプレフィックスであり、
$this->viewPrefix.'templates.'
はvendor/azuki/system/templates
ディレクトリのテンプレートを使うことを
設定しいます。
なお、vendor/azuki/system/templates
は汎用テンプレートになっています。
他に'pageName'
ならびに'controller'
は各コントローラーで個別になる場所ですので、合わせて設定が必要です。
'controller'
の設定はURLのパスに使用されます。
今回汎用テンプレートを使う形にしているのでテンプレートファイルの作成をしませんが、専用のテンプレートを指定した場合は
テンプレートファイルの作成が必要です。
テンプレートファイルは各画面に対してファイル名が決まっていますので、その点注意してください。
routes/web.php
に以下のルーティングルールを追加します
Route::group(['prefix' => 'system', 'middleware' => ['auth.system', 'acl']], function($route) {
$route->get('information',
['uses' => 'System\\InformationController@getIndex', 'as' => 'system.information.index']);
$route->get('information/list',
['uses' => 'System\\InformationController@getList', 'as' => 'system.information.list']);
$route->post('information/list',
['uses' => 'System\\InformationController@postList', 'as' => 'system.information.p-list']);
$route->get('information/regist',
['uses' => 'System\\InformationController@getRegist', 'as' => 'system.information.regist']);
$route->post('information/regist',
['uses' => 'System\\InformationController@postRegist', 'as' => 'system.information.p-regist']);
$route->get('information/detail/{id}',
['uses' => 'System\\InformationController@getDetail', 'where' => ['id', '[0-9]+'], 'as' => 'system.information.detail']);
$route->post('information/edit',
['uses' => 'System\\InformationController@postEdit', 'as' => 'system.information.p-edit']);
$route->get('information/edit/{id}',
['uses' => 'System\\InformationController@getEdit', 'where' => ['id' => '[0-9]+'], 'as' => 'system.information.edit']);
$route->get('information/edit/{id}/prev/{prev}',
['uses' => 'System\\InformationController@getEdit', 'where' => ['id' => '[0-9]+', 'prev' => 'detail|list'], 'as' => 'system.information.edit']);
$route->post('information/confirm',
['uses' => 'System\\InformationController@postConfirm', 'as' => 'system.information.p-confirm']);
$route->post('information/done',
['uses' => 'System\\InformationController@postDone', 'as' => 'system.information.p-done']);
$route->post('information/delete',
['uses' => 'System\\InformationController@postDelete', 'as' => 'system.information.p-delete']);
});
Azukiシステムのルーティングを使用した場合は以下の記述方法が可能です。
ただし、web.phpに記述するのではなく別ファイルに記述の上、App\Providers\RouteServiceProvider
クラスの
mapWebRoutes
メソッドから読み込むようにする方がよいと考えます。
$routeController = new \Azuki\App\Services\RouteControll([
'web' => [
'namespace' => '\App\Http\Controllers',
'group' => [
// /system/~ に関するルーティング。ミドルウェアにてログイン、権限の制限がかかったURL
[
'prefix' => 'system',
'middleware' => ['auth.system', 'acl'],
'list' => [
[ // システムユーザー管理画面
'prefix' => 'information',
'set' => [
[ 'method' => 'get', 'url' => 'index' ],
[ 'method' => 'get', 'url' => 'list' ],
[ 'method' => 'post', 'url' => 'list' ],
[ 'method' => 'post', 'url' => 'regist' ],
[ 'method' => 'get', 'url' => 'regist' ],
[ 'method' => 'get', 'url' => 'detail/{id}', 'where' => ['id' => '[0-9]+'] ],
[ 'method' => 'post', 'url' => 'edit' ],
[ 'method' => 'get', 'url' => 'edit/{id}', 'where' => ['id' => '[0-9]+'] ],
[ 'method' => 'get', 'url' => 'edit/{id}/prev/{prev}', 'where' => ['id' => '[0-9]+', 'prev' => 'detail|list'] ],
[ 'method' => 'post', 'url' => 'confirm' ],
[ 'method' => 'post', 'url' => 'done' ],
[ 'method' => 'post', 'url' => 'delete' ],
],
'controller' => 'InformationController',
],
],
],
],
]
]);
$routeController->makeRouting(app('router'));
http[s]://ドメイン名/system/information
にアクセスして、お知らせの登録・編集・削除が出来ることを確認します
マイページへの表示のみ紹介します。
Models/Informations
に表示場所を引数指定して最新5件を取得できるメソッドを作成します。
これは共通で利用できるメソッドになります。
/**
* function getLastInformations
*
* 最新のお知らせ情報を取得する
*
* @params Integer $place 表示場所の指定
* @params Integer $num 取得件数ーデフォルト5件
*
* @return Collection $ret Informationsクラスのインスタンスコレクション
**/
public function getLastInformations($place, $num = 5)
{
$ret = $this
->where('place', '=', $place)
->where('is_display', '=', 1)
->orderBy('datetime', 'desc')
->limit($num)
->get();
return $ret;
}
次にコントローラーを作成し、既存のマイページ用のコントローラーを継承し、必要なメソッドのみオーバーライドします。
App\Http\Controllers\Common\MypageController
を以下の内容で作成します。
namespace App\Http\Controllers\Common;
use Azuki\App\Http\Controllers\Common\MypageController as Controller;
use App\Models\Informations;
class MypageController extends Controller
{
/**
* mypageのテンプレート個別設定
* myapgeトップ画面のテンプレートのみお知らせ表示のために差し替える
*/
protected $viewTemplates = [
'mypage' => 'mypage.mypage',
];
/**
* function indexAction 初期表示のアクション
*
* @Get("/", as="index")
*
* 初期表示のアクション
* 初期表示は一覧とするため、一覧のアクションにリダイレクト
* @return void
*/
public function getIndex()
{
$id = $this->user->id;
$forms = $this->getDetailForm();
$this->ctrlSupporter->setDetailForm($forms);
$this->ctrlSupporter->setResultStatus();
$info = new Informations();
$information = $info->getLastInformations(2);
return $this->ctrlSupporter->view('mypage', [
'flow' => 'mypage',
// 'prev' => $prev,
'post' => $this->ctrlSupporter->find($id),
'detailLayout' => $this->getDetailLayout(),
'detailForm' => $forms,
'informations' => $information,
]);
}
}
対応するテンプレートファイルを作成します。上記のコンロトーラーで
protected $viewTemplates = [
'mypage' => 'mypage.mypage',
];
と設定しているので、対象のテンプレートはresources/views/mypage/mypage.blade.php
となります。
このファイルを作成し、以下のような感じにします。
@extends($vendor.'.'mypage.mypage'')
@section('contents-body')
<div class="in-contents-body">
<h2>お知らせ</h2>
@forelse($informations as $row)
{{$row->datetime}}{!!nl2br(e($row->contents))!!}
@empty
お知らせはありません
@endforelse
@parent
@stop
最後にルーティングを変更し、上記で作成したコントローラーに処理が渡るようにします。
routes/web.php
に以下を追加します。
Route::get('/mypage', ['uses' => 'Common\\MypageController@getIndex', 'as' => 'mypage']);
Ver.1.2.x以降ではルーティングの上書きをせずに、コントローラーの実体を切り替えることができます。
config/azuki.app.php
をpublishし以下を設定します。
'aliases' => [
'controllers' => [
'common' => [
'mypage' => \App\Http\Controllers\Common\MypageController::class, // [マイページ]
],
],
],
config/azuki.app.php
の他の設定を削除しないようにしてください