以前「Laravel5.4でシンプルなCMSを作るチュートリアル」というのを書いたのですが、バージョンが古くなったりしているので新しいバージョンで書き直した記事になります。
作る機能
インストール
最初にLaravelをComposerでインストールします。
バージョン指定しない場合、現在(2020年10月)の最新バージョンである8がインストールされます。
$ composer create-project --prefer-dist laravel/laravel LaravelMiniCMS
バージョンを指定する場合はコマンドの最後にバージョンを追記します。
$ composer create-project --prefer-dist laravel/laravel LaravelMiniCMS "8.*"
インストールにしばらく時間が掛かりますが、終わったらインストールしたディレクトリをカレントにします。
$ cd LaravelMiniCMS
ビルドインサーバーを起動してとりあえず動くか確認してみましょう。
$ php artisan serve
ブラウザでhttp://localhost:8000/
にアクセスしてLaravelって表示されれば成功です。
開発用ライブラリ
Laravelの開発を始めるにあたり便利なライブラリをいれておきましょう。
Laravel IDE Helper
IDEを使用していると自動解析でエラーになることがありますが、このライブラリを入れておくと正常に表示できるようになります。
その他にも補完ができるようにしてくれたりします。
IDEを使用している場合は基本入れておきましょう。
$ composer require barryvdh/laravel-ide-helper --dev
インストールしたらファイルを生成します。
$ php artisan ide-helper:generate
モデル作成後もモデルに対応したファイルを生成することができます。
$ php artisan ide-helper:models "App\Models\Post"
Laravel Debugbar
このライブラリはブラウザの開発ツールのようなパネルを表示してくれます。
セッションやどのようなSQLが発行されているか確認することができます。
$ composer require barryvdh/laravel-debugbar --dev
インストールしたらファイルを生成します。
$ php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider"
Laravel Collective
Laravel Collectiveはフォーム処理を短く書けるヘルパー集です。
最近メンテナンスされてない感があり、ちょっと不安な感じはするのですが、Laravel8では使用することができるようです。
$ composer require laravelcollective/html
※インストール時メモリが不足しているエラーが表示される場合は「COMPOSER_MEMORY_LIMIT=-1」を付けると一時的にメモリ制限をなくせます。
$ COMPOSER_MEMORY_LIMIT=-1 composer require
初期設定
configのapp.phpを開き、言語設定をします。
config/app.php
return [ // ... 'timezone' => 'Asia/Tokyo', 'locale' => 'ja', 'faker_locale' => 'ja_JP',
次はデータベースの設定です。
デフォルトではMySQLですが、今回はSQLiteを使用します。
.env
を開いて、DB_CONNECTION=sqlite
を追記して既存のMySQLの設定部分は削除します。
.env
DB_CONNECTION=sqlite #DB_CONNECTION=mysql #DB_HOST=127.0.0.1 #DB_PORT=3306 #DB_DATABASE=homestead #DB_USERNAME=homestead #DB_PASSWORD=secret
touchコマンドでsqliteファイルを作成。
$ touch database/database.sqlite
設定を変更したら、ビルドインサーバーを再起動しましょう。
ファイルの雛形を作成
一つ一つファイルを作成してもいいのですが、LaravelにはMVCに必要なファイルの雛形を一括で生成してくれるコマンドがあります。
次のコマンドを実行してみましょう。
$ php artisan make:model Post -a
Postテーブルの作成
どのようなデータを扱うかテーブルの設定をします。
先ほどのコマンドを実行していると、database/migrations
ディレクトリに日付+create_posts_table.php
のファイル名で生成されています。
次のように編集しましょう。
database/migrations/XXXX_XX_XX_XXXXXX_create_posts_table.php
public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body')->nullable(); $table->boolean('is_public')->default(true)->comment('公開・非公開'); $table->dateTime('published_at')->default(DB::raw('CURRENT_TIMESTAMP'))->comment('公開日'); $table->timestamps(); }); }
次のコマンドを実行することでデータベースにテーブルが作成されます。
$ php artisan migrate
ダミーデータの作成
作成したテーブルに登録する仮のデータを作成します。
最初はある程度の量のデータを入れておいた方がいいと思いますので、Fakerというランダムデータを生成する機能を使ってみましょう。
PostFactory.php
を次のように編集します。
database/factories/PostFactory.php
<?php namespace Database\Factories; use App\Models\Post; use Illuminate\Database\Eloquent\Factories\Factory; class PostFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ protected $model = Post::class; /** * Define the model's default state. * * @return array */ public function definition() { $random_date = $this->faker->dateTimeBetween('-1year', '-1day'); return [ 'title' => $this->faker->realText(rand(20,50)), 'body' => $this->faker->realText(rand(100,200)), 'is_public' => $this->faker->boolean(90), 'published_at' => $random_date, 'created_at' => $random_date, 'updated_at' => $random_date ]; } }
シーダーファイルで作成したファクトリーを実行します。
count
の部分に作成したいデータの数を指定しましょう。
50件登録するには次のようにします。
database/seeds/PostSeeder.php
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use App\Models\Post; class PostSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { Post::factory()->count(50)->create(); } }
次にこのシーダーが実行されるようにDatabaseSeeder.php
に登録します。
database/seeders/DatabaseSeeder.php
public function run() { $this->call(PostSeeder::class); }
次のコマンドを実行するとデータが登録されます。
$ php artisan db:seed
これでpostsテーブルにダミーデータが登録されました。
Postモデルの作成
fillable
に更新できるプロパティを設定します。
id,created_at,updated_atはDBとフレームワークの機能で更新するのでここには書きません。
casts
にはどのような型を扱うかを設定します。
is_publicはboolean、published_atはdatetimeで扱いたいので設定しておきましょう。
app/Post.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Post extends Model { use HasFactory; protected $fillable = [ 'title', 'body', 'is_public', 'published_at' ]; protected $casts = [ 'is_public' => 'bool', 'published_at' => 'datetime' ]; }
モデルを作ったらIDEヘルパーのコマンドを実行して補完できるようにしておきましょう。
$ php artisan ide-helper:model
ルーティング設定
今回は大きく一般ユーザーが閲覧できる画面とデータを管理する管理画面の2つのルートで分けます。
Laravelのルーティングroutes/web.php
にそのまま記述してもいいですが、今回は一般ユーザー用のfront.php
と、管理画面用のback.php
の2つのファイルに分けてみます。
ルーターファイルをroutes/web.php
から変更する場合はRouteServiceProvider.php
を編集します。
app/Providers/RouteServiceProvider.php
<?php namespace App\Providers; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { // ... protected $namespace = 'App\\Http\\Controllers'; public function boot() { $this->configureRateLimiting(); $this->routes(function () { Route::prefix('api') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); // フロント画面 Route::middleware('web') ->namespace($this->namespace . '\Front') ->as('front.') ->group(base_path('routes/front.php')); // 管理画面 Route::prefix('admin') ->middleware('web') ->namespace($this->namespace . '\Back') ->as('back.') ->group(base_path('routes/back.php')); }); } // ... }
middleware
にコントローラーのネームペースを設定して、as
はリンクを設定するときのルート名を設定しています。
routes
ディレクトリにfront.php
とback.php
ファイルを作成し、ひとまず下記のようにします。
routes/front.php
<?php use Illuminate\Support\Facades\Route; Route::get('/', function () { echo 'front'; });
routes/back.php
<?php use Illuminate\Support\Facades\Route; Route::get('/', function () { echo 'back'; });
http://localhost:8000
にアクセスして、「front」が、
http://localhost:8000/admin
にアクセスして、「back」が表示されることを確認してください。
Postコントローラーの作成
フロントのコントローラーから作っていきます。
makeコマンドでapp/Http/Controllers
ディレクトリ直下にPostController.php
が作られていると思いますが、先ほどの設定でFront
ディレクトリに配置するように変更したので移動しましょう。
そして次のように編集します。
app/Http/Controllers/Front/PostController.php
<?php namespace App\Http\Controllers\Front; use App\Http\Controllers\Controller; use App\Models\Post; class PostController extends Controller { /** * 一覧画面 * * @return \Illuminate\Contracts\View\View */ public function index() { // 公開・新しい順に表示 $posts = Post::where('is_public', true) ->orderBy('published_at', 'desc') ->paginate(10); return view('front.posts.index', compact('posts')); } /** * 詳細画面 * * @param int $id * @return \Illuminate\Contracts\View\View */ public function show(int $id) { $post = Post::where('is_public', true)->findOrFail($id); return view('front.posts.show', compact('post')); } }
showアクションは初期状態では引数にモデルバインディングがしてある状態ですが、is_publicで検索する必要があった為、idに変更しています。
ビューレイアウトの作成
次にビューファイルを作成しますが、ナビゲーションなど共通で使用するファイルをレイアウトファイルとして作ります。
LaravelではデフォルトではBladeというテンプレートライブラリを使用します。
このライブラリにはテンプレートを継承する機能がありこれを使用すればレイアウトを実現できます。
resources/views/front/layouts/base.blade.php
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ isset($title) ? $title . ' | ' : '' }}Laravel CMS</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous"> </head> <body> <div id="app"> <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm"> <div class="container"> <a class="navbar-brand" href="{{ route('front.home') }}"> Laravel CMS </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav ml-auto"> <li class="nav-item{{ Request::is('/') ? ' active' : '' }}"> <a class="nav-link" href="{{ route('front.home') }}">ホーム</a> </li> <li class="nav-item{{ Request::is('posts', 'posts/*') ? ' active' : '' }}"> <a class="nav-link" href="{{ route('front.posts.index') }}">お知らせ</a> </li> </ul> </div> </div> </nav> <main class="py-4"> <div class="container"> <div class="row justify-content-center"> <div class="col-md-12"> <div class="card"> @yield('content') </div> </div> </div> </div> </main> </div> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" crossorigin="anonymous"></script> </body> </html>
@yield('content')
の部分にこれから作る各ビューファイルの内容が入ります。
Post一覧(index)ビューの作成
コントローラーのindexに対応したビューファイルです。
ビューファイルはmakeコマンドで作成されていないので、普通に作ります。
link_to_route
は最初にインストールした、Laravel Collectiveの機能です。
resources/views/front/posts/index.blade.php
<?php /** * @var Illuminate\Pagination\LengthAwarePaginator|\App\Models\Post[] $posts */ $title = '投稿一覧'; ?> @extends('front.layouts.base') @section('content') <div class="card-header">{{ $title }}</div> <div class="card-body"> @if($posts->count() <= 0) <p>表示する投稿はありません。</p> @else <table class="table"> @foreach($posts as $post) <tr> <td>{{ $post->published_at->format('Y年m月d日') }}</td> <td>{!! link_to_route('front.posts.show', $post->title, $post) !!}</td> </tr> @endforeach </table> <div class="d-flex justify-content-center"> {{ $posts->links() }} </div> @endif </div> @endsection
$posts->links()
はページャーナビゲーションの部分なのですが、このままだと崩れていると思います。
Laravel8ではTailwindcssというCSSのフレームワークがベースになっている為です。
今回はBootstrapを読み込んでいるので変更しましょう。
app/Providers/AppServiceProvider.php
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Pagination\Paginator; class AppServiceProvider extends ServiceProvider { // ... /** * Bootstrap any application services. * * @return void */ public function boot() { Paginator::useBootstrap(); } }
これでページャーナビゲーションの部分が正常に表示されるようにりました。
Post詳細(show)ビューの作成
コントローラーのshowに対応したビューファイルです。
resources/views/front/posts/show.blade.php
<?php /** * @var \App\Models\Post $post */ $title = '投稿詳細'; ?> @extends('front.layouts.base') @section('content') <div class="card-header">{{ $title }}</div> <div class="card-body"> <h2>{{ $post->title }}</h2> <time>{{ $post->published_at->format('Y年m月d日') }}</time> <div>{!! nl2br(e($post->body)) !!}</div> {!! link_to_route( 'front.posts.index', '一覧へ戻る', null, ['class' => 'btn btn-secondary']) !!} </div> @endsection
ルーターの設定
最後にコントローラーにアクセスできるようにルーターを編集します。
トップにアクセスするとPostController
のindex
が表示されるようにして、
posts
でPostController
がアクセスできるようにします、フロントではindex
とshow
だけにアクセスしたいのでonly
で指定します。
routes/front.php
Route::get('/', 'PostController@index')->name('home'); Route::resource('posts', 'PostController')->only(['index','show']);
モデルスコープでクエリをコントローラーから移動する
このままでも問題はないのですが、Laravelの機能を使用してもう少し良いコードにしてみましょう。
現在のコントローラーをみてみるとこんな感じでDBのクエリが入っています。
app/Http/Controllers/Front/PostController.php
$posts = Post::where('is_public', true) ->orderBy('published_at', 'desc') ->paginate(10);
このままですと、他のコントローラーで同じクエリを実行したいとき、同じ処理を書かなければいけません。
スコープという機能を使用してモデルに処理を移すことでこの問題を解決してみましょう。
app/Models/Post.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; class Post extends Model { // ... // 公開のみ表示 public function scopePublic(Builder $query) { return $query->where('is_public', true); } // 公開記事一覧取得 public function scopePublicList(Builder $query) { return $query ->public() ->latest('published_at') ->paginate(10); } // 公開記事をIDで取得 public function scopePublicFindById(Builder $query, int $id) { return $query->public()->findOrFail($id); } }
これで公開Postの一覧はPost::publicList()
で、詳細画面ではPost::publicFindById($id)
でデータを取得できるようになります。
public function index() { $posts = Post::publicList(); return view('front.posts.index', compact('posts')); } public function show(int $id) { $post = Post::publicFindById($id); return view('front.posts.show', compact('post')); }
コントローラーの記述がすっきりしましたね。
ゲッターを使用してフォーマットを共通で使う
ビューファイルの公開日(created_at)は年月日で表示しているのですが、この書式は一覧と詳細画面にありますね。
$post->created_at->format('Y年m月d日')
日付フォーマットは同じものを使うことが多いので、この部分の処理をまとめましょう。
この場合はモデルのゲッターという機能を使用します。
ゲッターはモデルにget●●●Attribute
という名前でメソッドを作ります。
app/Models/Post.php
<?php // ... class Post extends Model { // ... // 公開日を年月日で表示 public function getPublishedFormatAttribute() { return $this->published_at->format('Y年m月d日'); } }
ビューで次のようにしていすることで年月日で表示できるようになります。
{{ $post->published_format }}
今回は一般ユーザー向けのフロント表示部分の作成をしました。
次回は管理画面に入る為のログイン機能を作っていきます。
ソースコードはGitHubに置いてます。
LaravelMiniCMS2020