LoginSignup
29

More than 5 years have passed since last update.

Laravel5入門として画像投稿機能付き掲示板作成を目指す:基本編 〜備忘録垂れ流し〜

Last updated at Posted at 2016-02-01

自分用の備忘録として、画像投稿機能付き掲示板の作成をメモしていこうと思います。
ララ帳様を参考にして作成していきます。以下、ララ帳様からの引用が多々あります。

以下、おおざっぱな目標です
・PHPで実装
・フレームワークはLaravel5を使う
・サーバーはXAMPP
・DBはphpmyadmin
・bootstrap3を使って見栄えを良くする

Part1

初めに

最初にcomposer create-project laravel/laravel --prefer-dist rookieLaravel5にてプロジェクトを作成。
そして以下の設定をすませる。
- config/app.php: timezone を Asia/Tokyo に、 locale を ja に。
- config/database.php: default に mysql を設定

Permissionの設定

本来であれば
chown -R USER_NAME:GROUP_NAME storage
chown USER_NAME:GROUP_NAME bootstrap/cache

としたほうが良いのですが、今回は
chmod -R 777 storage
で済ませました。
セキュリティ周りが怖いので、のちのち変えます。

実行

PHPのビルドインサーバーを使って
php -S localhost:8000 -t public
または
php artisan serve
としても良いのですが、今回はXAMPPを使用します。

サーバーを立ち上げて
http://localhost/rookieLaravel5/public に飛ぶと、Laravel5の文字が表示されました

やったこと

  • Laravel5のプロジェクトを作成

これから全てが始まります。

Part2

今回はMVCの User -> Routing -> Controller -> View の流れを追ってみたいと思います。

ララ帳の2回目にあたりますね

学び

MVCの考え方は

  1. User は routes.php にリクエストを送る
  2. routes.php はリクエストに応じて controller に指令を送る
  3. controller は routes.php から送られてきた指令に応じた処理をModelに依頼する
  4. Modelが処理したデータや情報をViewに渡す
  5. ViewはModelから受け取ったデータをUserに見やすい形に直す
    • View は blade というテンプレートエンジンを使ってHTMLを生成している
    • テンプレートエンジンとはデータとテンプレートを組み合わせ文字列を生成する仕組みのこと

といった感じでしょうか。

Part3

今回はController周りの話です。

学び

  • php artisan make:controller WelcomeController でコントローラーを生成できる
    • artisan はLaravelを構成しているコマンドラインインターフェイスの名前
  • routes.php に Route::get('contact', 'WelcomeController@contact');と記述することによってリクエストに応じた処理をコントローラーに投げることができる
  • コントローラー内でreturn view('pages.about');という記述は pages/about.blade.php を 呼び出す

part4

Part3では静的なページ表示でしたが、今回はViewに値を渡して動的にページ表示が出来るようになるようです。

学び

  • $data["first"] = "Yama" として適当に配列を作成
  • return view('about', $data); のように、View関数の第2引数に渡したいデータを記述
  • Viewでは 受け取ったデータは <?php echo $first ?> のようにして使う
    • なぜ $data["first"] ではないんだろう…
  • <?php echo $first ?>{{ $first }} は表示上は等価な処理?
    • {{ }} ダブルカーリーは blade の機能の1つ
  • return view('about', compact('last'));のように compact関数を使えば、ローカル変数を配列にして渡すことができる

Part5

bladeでのレイアウト作成

学び

  • bladeファイル中の yield@('contact') は実際の文章を流し込む部分
    • ('contact')部分はフィールドにタグ付けしている部分
  • Viewファイルで@extend('layout')とすることで bladeテンプレートを使用することを宣言
  • Viewファイル内で @section('content') ~~ @endsection で~~部分をcontentラベルのyieldフィールドに文章を流し込める
  • php artisan help コマンド名 でコマンドのhelpを開くことができる

Part6

DBの設定編です。

学び

  • PHP:app/database.php内のenv(‘DB_HOST’, ‘localhost’) は .envファイルの DB_HOSTに 割り当てられているVALUEを取得する。VALUEがない場合は第2引数の localhost が使われる
  • php artisan migrateでテーブルを作成
    • マイグレーションはデータベースのバージョンコントロールの一種

Part7

マイグレーション機能の実行

学び

  • php artisan make:migration create_articles_tableでマイグレーションファイルの作成
    • 以下のようにして作成するテーブルを定義する
create_users_table.php
public function up()
      {
          Schema::create('articles', function (Blueprint $table) {
              $table->increments('id');
              $table->string('title');
              $table->text('body');
              $table->timestamps();
          });
      }

      /**
       * Reverse the migrations.
       *
       * @return void
       */
      public function down()
      {
          Schema::drop('articles');
      }
  • app/以下にArticles.php(Model)を追加
Articles.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    // @var string
    protected $table = 'articles';

    //@var array
    protected $fillable = ['title', 'body'];
}
  • php artisan migrateを実行
    • [PDOException]SQLSTATE[HY000] [2002] No such file or directory
      となってしまいそのままでは実行できないので
      config/database.phpに
      'unix_socket' => env('DB_SOCKET', '/Applications/XAMPP/xamppfiles/var/mysql/mysql.sock')
      を追加
    • migrationを実行すると、DB:mylaravelに TABLE:articles が作成される
  • migrationファイルは マイグレーション実行時のバージョンアップ処理をup()に、ロールバック実行時の処理を down()に
  • Scherma::XX はDBを操作している部分でありスキーマビルダーと呼ぶ

Part8

学び

  • マイグレーションの実行順序を示すために、migrationファイルにはタイムスタンプが振られている
  • php artisan make:migration create_articles_table --create=articlesで作成オプション付きのマイグレーションファイルを作成
  • テーブルの変更には2つの方法がある
    1. 一旦ロールバックして、マイグレーションファイルに項目を追加してから、再度マイグレーションを実行する方法
    2. 項目追加用のマイグレーションファイルを作成して、マイグレーションを実行する方法
  • ここでは2つ目の方法を試す
    • php artisan make:migration add_published_at_to_articles_table --table=articles でテーブル変更用のファイルを作成(--tableオプションをつける)
    • 生成されたファイルの up()に
      $table->timestamp('published_at')->nullable();
      down()に
      $table->dropColumn('published_at');
      をそれぞれ追加
    • php artisan migrateを実行すると、テーブルが変更されている
    • php artisan migrate:rollbackでロールバックを行う

Part9

モデルを使ってのDB操作

学び

  • Eloquent : DBとモデルオブジェクトを対応付ける機能
  • Model : Eloquent の機能を継承し、ビジネスロジックを加えたクラス
  • php artisan make:model Articleで Article.php を作成A
  • php artisan tinkerで対話的に操作できる(すごい)
    • $article = new App\Article();:Articleクラスをnewすることで、メモリ上に新規のArticleインスタンスを作成
    • $article->title = "first!!";:値をセット
    • $article->published_at = Carbon\Carbon::now();:時刻をセット
    • $article->save():データベースに保存
    • Articleクラスにはアクセサが定義されていないので、直接値にアクセスできる
    • App\Article::all()->toArray();で全件表示できる

Part10

ELOQUENTを触ってみる

学び

  • App\Article::find(1)->toArray();でIDを指定して取得できる
  • App\Article::findOrFail(3)->toArray();で例外指定
  • App\Article::where('id', '>', 0)->get()->toArray();で条件指定
  • App\Article::where('id', '>', 1)->count();で条件指定したうえで集計できる 

ELOQUENT、便利ですね

Part11

ELOQUENT マスアサインメントを使ってみます

学び

  • マスアサインメント:複数のカラムを指定して、一気に変更できる機能のこと
  • モデルをマスアサインメントを使えるように変更
    • fillableを指定することによって、想定外の入力を避ける
Articles.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $fillable = ['title', 'body', 'published_at'];
}
  • php artisan tinker
    App\Article::create(['title' => '3件目の記事', 'body' => '吾輩は猫である', 'published_at' => Carbon\Carbon::now()]);
    を実行すると、テーブルに追加される

Part12

ELOQUENTミューテーターを触ります。

学び

  • ミューテーター: モデルの属性を設定したり取得したりする時に、内容を変更できる便利な方法のこと
  • ゲットミューテーター
    • 例)モデルにタイトルを大文字で返す機能を追加
Article.php
~~
public function getTitleAttribute($value)
    {
        // 大文字に変換
        return mb_strtoupper($value);
    }
~~
  • セットミューテーター
    • 例)モデルに本文を小文字で登録する機能を追加
Article.php
~~
public function setBodyAttribute($value){
    $this->attributes['title'] = mb_strtolower($value);
}
~~
  • 日付ミューテーター
    • Elocuentにはデフォルトで日付ミューテーターが備わっていて、created_at, updated_at 属性はPHPのDateTimeを拡張したCarbonのインスタンスに変換される
    • 日付ミューテーターを使用する属性をEloquentに知らせるには、dates配列に属性名を追加する
    • created_at 属性が Carbonクラスに変更される
Article.php
~~
// published_at で日付ミューテーターを使う
    protected $dates = ['published_at'];
~~

Part13

DBのシードを触ります

学び

  • artisan db:seed コマンドを使って、初期データをDBに埋め込むことができる
    • システムに必要な初期データを作成したり、開発で使用するサンプルデータを作成するため
  • シーダーファイルを編集
    • unguard()メソッドは、EloquentのマスアサインメントをOFF
    • reguard()メソッドは、EloquentのマスアサインメントをON
    • php artisan db:seed: DatabaseSeederクラスの run() メソッドが実行される
    • run()メソッドの中から、ArticlesTableSeederをコールしている
    • Query Builderを使って、Articlesテーブルのレコードを全て削除できる
    • Fakerを使用してダミーデータの作成
      • Laravel 5.1では標準でFackerが使用できるようになっている
database/seeds/DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

use Faker\Factory as Faker;
use Carbon\Carbon;
use App\Article;

class DatabaseSeeder extends Seeder{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();  //
        $this->call(ArticlesTableSeeder::class);
        Model::reguard();  //
    }
}

class ArticlesTableSeeder extends Seeder{
    public function run(){
        DB::table('articles')->delete();
        $faker = Faker::create('en_US');
        for ($i = 0; $i < 10; $i++) {
            Article::create([
                'title' => $faker->sentence(),
                'body' => $faker->paragraph(),
                'published_at' => Carbon::today()
            ]);
        }
    }
}

faker便利ですね

Part14

コントローラからモデルに問い合わせ、モデルがDBからデータを取り出してコントローラに渡し、ビューで表示する一連の流れを触ります。

学び

  • Route::get('articles/{id}', 'ArticlesController@show');
    • {id}部分はshowに引数を渡している部分
  • Articleコントローラを作成
    • php artisan make:controller ArticlesController
    • --plain はないと言われてしまう…
    • index() , show() を作成
app/Http/Controllers/ArticlesController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Article; \\追加

class ArticlesController extends Controller
{
    public function index() {
        $articles = Article::all();

        return view('articles.index', compact('articles'));
    }

    public function show($id){
        $article = Article::findOrFail($id);

        return view('articles.show', compact('article'));
   }
}
  • indexのビューを作成
resources/views/articles/index.blade.php
@extends('layout')

@section('content')
    <h1>Articles</h1>
    <hr/>
    @foreach($articles as $article)
        <article>
            <h2>
                <a href="{{url('articles', $article->id) }}">
                    {{ $article->title }}
                </a>
            </h2>
            <div class='boby'>
                {{ $article->body }}
            </div>
        </article>
    @endforeach
@endsection
  • 記事のタイトルは urlヘルパ関数で、’articles/{id}’へのリンクを張っている
    • 理解のために、<a href='contant'>とすると、contactのビューが表示されることが確認できた
  • @foreachで1件づつ 記事の作成を繰り返している

  • showのビューを作成

resources/views/articles/show.blade.php
@extends('layout')

@section('content')
    <h1>{{ $article->title }}</h1>
    <hr/> //水平線を引く
    <article>
        <div class="body"> {{ $article->body }} </div>
    </article>
@endsection
  • .env の APP_DEBUG = false とすると、デバッグ情報が表示されなくなる

php artisan tinkerで色々触っていたおかげで、すんなり理解できました

番外編

前回のPartで関数に引数を渡す方法がわかったので、素数を数え上げるプログラムを書いてみたいと思います。

  • routes.php に Route::get('prime/{num}', 'MathController@show');を記述
  • MathControllerを作成し、show()を作成
app/Http/Controllers/MathController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class MathController extends Controller
{
    public function show($num) {
        return view('prime', compact('num'));
    }
}
  • ビューを作成
resources/views/prime.blade.php
@extends('layout')

@section('content')
    <h1> Max Number is {{ $num }} </h1>

    <?php
        for($i = 3; $i < $num; ++$i){
            $flag = 1;
            for($j = 2; $j < sqrt($i); ++$j){
                if($i % $j == 0){
                    $flag = 0;
                    break;
                }
            }
            if($flag == 1){
                print($i);
                print(' is prime.<br>');
            }
        }
    ?>

@endsection

ほんとはphp使わずに bladeの @for とか @if とかを使いたかったんですが、HTML上で変数を使う機能がわからなかったのと眠かったので断念…
Laravel感があんまり出てない感じになってしまいました。
MVCの流れに沿うならば
- Model で素数を計算し配列をViewに渡す
よしたほうがいいのかな。

Part15

そっけない表示をフラットデザイン風に変えてくれる素敵機能(らしい)Bootstrapを触ってみます。

  • Bootstrapの導入にはいくつか方法がある
    1. Bootstrap CDNを使用する
      • CDNとは、ファイルサイズの大きいデジタルコンテンツをネットワーク経由で配信するために最適化されたネットワークのこと
    2. コンパイル済のBootstrap をダウンロードして、public ディレクトリ以下に配置する
    3. Bootstrapのソースをダウンロードして、resources/assets ディレクトリに配置し、Elixirを使ってコンパイルおよび public ディレクトリに配置する
  • ここでは 1 の方法を採用
  • layoutファイルを編集
resources/views/layout.blade.php
<!DOCTYPE HTML>
<html lang="ja">
<head>
    <meta charset="UTF=8">
    <title>My Blog</title>

    <!-- CSSB$rDI2C --><!-- 追加1 -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

</head>
<body>
    <div class="container"><!-- 追加2 -->
        @yield('content')
    </div>

    <!-- Scripts --><!-- 追加3 -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

</body>
</html>
  • 1: Bootstrap CDN のCSSの読み込みを追加
  • 2: @yield(‘content’)を囲む div.container を追加。class=”container” を指定するとBootstrapが左右にマージンを表示
  • 3: jQuery CDNと Bootstrap CDNの JavaScriptの読み込みを追加。
    • jQuery(ジェイクエリー)は、ウェブブラウザ用のJavaScriptコードをより容易に記述できるようにするために設計された軽量なJavaScriptライブラリである(Wikipedia)

左右に余白ができ、文字もなんかスタイリッシュになった気がします笑

Part16

Fromを作っていきます

学び

  • Laravel のViewでFormを記述するには以下の2つの方法がある
    1. 直接HTMLを記述する方法
    2. ヘルパー関数を使う方法
  • 今回はlaravelcollective/html パッケージをインストールして、ヘルパー関数を使用する
    • このパッケージは、Laravel 4では標準で組み込まれていたが、Laravel 5 からは別パッケージになり、ユーザーコミュニティーによってメンテナンスされるようになった。
    • インストール composer require laravelcollective/html
    • config/app.php を編集して、 laravelcollective/html をLaravelに組込む
php.config/app.php
~~
    'providers' => [
       ~~
       // Add rookieLaravel5
        Collective\Html\HtmlServiceProvider::class,
     ],
~~

    'aliases' => [
        //Add rookieLaravel5
        'Form' => Collective\Html\FormFacade::class,
        'Html' => Collective\Html\HtmlFacade::class,
    ],
~~
  • ArticlesController@createへのルートを追加
    • 下記の追加1 は @show よりも先に記述する
    • articles/{id} に先に引っかからないようにするため
    • ルートは記述した順に、上からマッチングされる
php.app/Http/routes.php
~~
Route::get('articles', 'ArticlesController@index');

Route::get('articles/create', 'ArticlesController@create'); // 追加1

Route::get('articles/{id}', 'ArticlesController@show');

~~
  • ArticlesController.php に createメソッドを実装
    • 中身はビューを表示しているだけ
php.app/Http/Controllers/ArticlesController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Article;

class ArticlesController extends Controller
{
    public function index() {
        $articles = Article::all();

        return view('articles.index', compact('articles'));
    }

    public function show($id){
        $article = Article::findOrFail($id);
        return view('articles.show', compact('article'));
    }

    //追加
    public function create(){
        return view('articles.create');
    }
}
  • create.blade.php を新規に作成
    • {!! XXXX !!} は blade テンプレートの表記で、PHPで評価した結果を表示する時に使用
      • 以前に同じような表記で、{{ XXXX }}があるが
        • {!! XXXX !!} の方はエスケープ処理を行わない
        • {{ XXXX }} の方はエスケープ処理を行う
      • 今回はFormのHTMLをPHPで生成しているので、エスケープ処理を行わないように、{!! XXXX !!} を使っている
      • エスケープ処理とは、マークアップ言語やプログラミング言語で文字列を扱う際に、その言語の文法や処理系にとって特別な意味や機能を持つ文字や記号(およびその並び)を、一定の規則に従って別の文字列に置き換えること。
        • 例えば、HTMLはタグの記述に「<」「>」という記号を用いるため、この記号そのものを文字として表示することは本来できない。このとき、「<」「>」という特殊な表記法に置き換えることで、それぞれ「<」「>」と表示することができる。このような置き換え操作・処理のことをエスケープ処理という。
    • Form::XXXX という表記で追加インストールした laravelcollective/html パッケージ使用している
      • 一見するとFormクラスのstaticメソッドをコールしているように見えるが、実は Collective\Html\FormBuilder クラスのインスタンスメソッドをコールしている
      • ここではFormクラスというalias(別名)を使って、簡素に’Collective\Html\FormBuilder’のインスタンスにアクセスしているという理解でよい
      • この仕組みをファサードとよぶ
    • Formの使用例
      • Form::open() formの開始タグを生成
        • 以下の例ではForm::open() に 引数を渡し、urlを指定している
      • Form::close() formの終了タグを生成
      • Form::label() labelタグを生成
      • Form::input() inputタグを生成
      • Form::text() input[type=text]タグを生成
      • Form::textarea() textareaタグを生成
      • Form::submit() input[type=submit]タグを生成
      • 詳細は下記のAPIリファレンスで確認
    • class=”xxxx”の指定は、前回導入したBootstrap3の classを指定している
      • これを指定するとフォームの表示がかっこ良くなる。
php.resources/views/articles/create.blade.php
@extends('layout')

@section('content')
    <h1>Write a New Article</h1>
    <hr/>
    {!! Form::open(['url' => 'articles']) !!}
        <div class="form-group">
            {!! Form::label('title', 'Title:') !!}
            {!! Form::text('title', null, ['class' => 'form-control']) !!}
        </div>

        <div class="form-group">
            {!! Form::label('body', 'Body:') !!}
            {!! Form::textarea('body', null, ['class' => 'form-control']) !!}
        </div>

        <div class="form-group">
            {!! Form::label('published_at', 'Publish On:') !!}
            {!! Form::input('date', 'published_at', date('Y-m-d'), ['class' => 'form-control']) !!}
        </div>

        <div class="form-group">
            {!! Form::submit('Add Article', ['class' => 'btn btn-primary form-control']) !!}
        </div>

    {!! Form::close() !!}
@endsection
  • DBへの保存処理の実装
  • ルートの追加
    • ArticlesController@storeへのルートを追加
php.app/Http/routes.php
~~
Route::post('articles', 'ArticlesController@store');
~~
  • ArticlesController.php に storeメソッドを実装
    1. \Request::all() でフォームの入力値を全て取得している
      • このRequest クラスもファサードで、実態は Illuminate/Http/Request
      • コントローラの名前空間でファサードクラスを使うには、グローバルクラスとして指定する必要があるので、クラス名の前に \ (バックスラッシュ)を付ける
    2. マスアサインメント機能を使って、Articlesテーブルにデータを作成
    3. いままでのコントローラメソッドでは、view()メソッドを使って、ビューを表示していたが、ここでは redirect()を使って、記事一覧のURLへリダイレクトさせている
php.app/Http/Controllers/ArticlesController.php
~~
    public function store(){
        $inputs = \Request::all(); // 1

        Article::create($inputs); // 2

        return redirect('articles'); // 3
    }
~~
  • index.blade.phpに新規作成ボタンを追加
    • link_to を使って、’articles/create’へのリンクタグを作成
    • bootstrap3のclassを設定して、リンクタグをボタンの様に表示できる
php.resources/views/articles/index.blade.php
~~
    {!! link_to('articles/create', '新規作成', ['class' => 'btn btn-primary']) !!}
~~

ちょいコラム

ブラウザ上で確認すると、日本語が文字化けしていたのでset fileencoding=utf-8を .vimrc に書き加えました。今まではset fileencoding=iso-2022-jpになっていたから文字化けしていたのですね(^_^;)

Part17

入力データのチェックを触ってみます。

学び

  • Laravelのコントローラはメソッドの引数にタイプヒントでクラスを記述すると、そのクラスのインスタンスを自動生成して渡してくれる
  • $this->validate($request, $rules);エラーがあると、自動的に前の画面にリダイレクトしてくれる
  • ビューファイルをエラーが表示されるように編集
php.resources/views/articles/create.blade.php
@extends('layout')

@section('content')
    <h1>Write a New Article</h1>
    <hr/>

    @if (isset($errors) && count($errors) > 0)
        <div class="alert alert-danger">
                <ul>
                            @foreach ($errors->all() as $error)
                                            <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
    @endif
~~

実は、ここのエラーメッセージの表示で1時間ほどハマっていました。
\$errors はどこからでも参照できるはずなのに、「\$errorsなんてないよ!!」と言われ続けていたのです。
ググりまくっていたところ、同じ疑問にぶち当たった人が質問されていて、解決されていたので助かりました。
どうやら
- 「全リクエストの全ビューで、いつでも\$errors変数は利用できるのが、重要なポイントです。」
- なぜ上記の機能が実現できているかといいますと、エラーをセッションに入れているから
- そして、その機能は、Kernel.php の \$middlewareGroups['web'] 内に記載されているStartSessionミドルウェアとShareErrorsFromSessionミドルウェアで実現されている
とのことでした。
- \$errors変数を使用するのであれば、StartSessionミドルウェアとShareErrorsFromSessionミドルウェアをルーティングに適用する必要があります。
であるらしいので、webmiddlewareでルートを囲ってやり、以下のようにroutes.phpを編集しました。

php.app/Http/routes.php
~~
Route::group(['middleware' => ['web']], function () {
    Route::get('articles', 'ArticlesController@index');

    Route::get('articles/create', 'ArticlesController@create');

    Route::get('articles/{id}', 'ArticlesController@show');

    Route::post('articles', 'ArticlesController@store');

    Route::get('prime/{num}', 'MathController@show');
});
~~

無事、動きました。
助かった…

Part18

FormRequestを触ります。

学び

  • artisan を使って、Article用の FormRequestを生成
    • php artisan make:request ArticleRequest
  • ArticleRequest.php の編集
    • authorizeメソッドではリクエストに対する権限を設定できる
    • 例えば、現在ログイン中のユーザに権限が無ければ、falseを返す
    • 今のところ誰でもArticlesデータを追加できるように trueを返しておく
    • rulesメソッドでは、バリデーションルールを返す
php.app/Http/Requests/ArticleRequest.php
<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class ArticleRequest extends Request
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'title' => 'required|min:3',
            'body'  => 'required',
            'published_at' => 'required|date',
        ];
    }
}
  • ArticlesController.php でArticleRequestを使うように編集
    • storeメソッドで受け取るクラスを Illuminate\Http\Request から App\Http\Requests\ArticleRequest に変更
    • これだけで、今まで storeメソッド内で行っていた、validateが不要になる
    • エラーがあった時の前画面へのリダイレクトも ArticleRequestが行ってくれる

Part19

エラーメッセージの日本語化です。

学び

Part20

記事が日付昇順に表示される・公開日を将来と設定しても投稿できる、この2つの問題を解決するPartのようです。

学び

  • コントローラーを修正
    • latest(;pubkished_at)->get()とすることで、pubkished_at の latest順で get できるようです
php.app/Http/Controllers/ArticlesController.php
~~
class ArticlesController extends Controller
{
    public function index() {
//        $articles = Article::all();
        $articles = Article::latest('published_at')->get();
        return view('articles.index', compact('articles'));
    }
~~
  • 公開日が現在時刻以前の記事だけを取得するように修正
    • 上での修正点に whereで条件を追加
    • $articles = Article::latest('published_at') ->where('published_at', '<=', Carbon::now()) ->get();
  • 「公開日が現在時刻以前の記事」という条件は何度も使いそうな気がする
    • EloquentのScope機能を使って、リファクタリング
  • モデルにScore機能を追加
php.app/Article.php
~~
 //  published scopeを定義
    public function scopePublished($query) {
        $query->where('published_at', '<=', Carbon::now());
    }
~~
  • コントローラで scopeを使用するよう修正
php.app/Http/Controllers/ArticlesController.php
~~
use Carbon\Carbon;

class ArticlesController extends Controller {

    public function index() {
        $articles = Article::latest('published_at')->published()->get();

        return view('articles.index', compact('articles'));
    }
}
~~

Part21

記事の編集画面を実装

学び

  • 編集画面のフォームは新規登録画面のフォームとほぼ同じな為、Viewの Partial 機能を使って、フォームを共通部品化する
  • ルートを追加
    • update の方は patch にする
php.app/Http/routes.php
~~
Route::get('articles/{id}/edit', 'ArticlesController@edit');  // 追加
Route::patch('articles/{id}', 'ArticlesController@update');  // 追加
~~
  • ArticlesControllerにeditとupdateメソッドを追加
php.app/Http/Controllers/ArticlesController.php
~~
    public function edit($id){
        $article = Article::findOrFail($id);
        return view('articles.edit', compact('article'));
    }

    public function update($id, ArticleRequest $request){
        $article = Article::findOrFail($id);
        $article->update($request->all());
        return redirect(url('articles', [$article->id]));
    }
~~
  • updateの方は、\$idと\$requestを引数で受け取っている

    • $requestは以前にやったFormRequestを継承したArticleRequestクラスを使用
    • FormRequestを使うことで、フォームの入力データのチェックとエラーがあった時の前画面へのリダイレクトを自動的に行う
    • updateメソッド内では $idに対応する記事を取得し、入力データで記事を updateし、記事表示画面にリダイレクトしている
  • edit.blade.phpを作成

php.resources/views/articles/edit.blade.php
@extends('layout')

@section('content')
    <h1>Edit: {{ $article->title }}</h1>

    <hr/>

    @if ($errors->any())
        <ul class="alert alert-danger">
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    @endif

    {!! Form::model($article, ['method' => 'PATCH', 'url' => ['articles', $article->id]]) !!}
    <div class="form-group">
        {!! Form::label('title', 'Title:') !!}
        {!! Form::text('title', null, ['class' => 'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::label('body', 'Body:') !!}
        {!! Form::textarea('body', null, ['class' => 'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::label('published_at', 'Publish On:') !!}
        {!! Form::input('date', 'published_at', $article->published_at->format('Y-m-d'), ['class' => 'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::submit('Edit Article', ['class' => 'btn btn-primary form-control']) !!}
    </div>
    {!! Form::close() !!}

@endsection
  • create.blade.phpから修正したのは以下の4点

    1. ページタイトルに記事のタイトルを表示
    2. Formタグの作成を From::open から Form::modelに変更
      • $articleの値をフォームに紐付ける為に Form::model を使用
      • METHODに PATCHを指定し、urlへは\$articleの idをパラメータとして引き渡す
    3. published_at の入力項目の初期値を date(‘Y-m-d’)から \$article->publieshed_atの値に変更
      • \$article->published_at はArticleクラスで日付ミューテーターを指定している為、参照するとCarbonクラスのインスタンスが返ってくる
      • format()メソッドで文字列に変換して初期値とする
    4. submit ボタンのタイトルを ‘Edit Article’ に変更
  • 新規記事作成フォームと、記事編集フォームで同じコードが重複しているので、Viewの Partial機能を使ってリファクタリングを行う

  • エラー表示の PARTIAL化

    • form_errors.blade.php を作成
php.resources/views/errors/form_errors.blade.php
@if ($errors->any())
    <ul class="alert alert-danger">
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
@endif
  • form_errors.blade.phpを使用するように、edit.blade.phpを修正
php.resources/views/articles/edit.blade.php
@extends('layout')

@section('content')
    <h1>Edit: {{ $article->title }}</h1>
    <hr/>

    @include('errors.form_errors)

    {!! Form::model($article, ['method' => 'PATCH', 'url' => ['articles', $article->id]]) !!}
    ~~
    {!! Form::close() !!}

@endsection
  • @includeを使って、form_errors.blade.php を参照する

    • form_errors.blade.phpは resources/views以下の errorsディレクトリに作成した為、’errors.form_errors’と指定する
    • この様に、@include することを目的に切り出した bladeテンプレートファイルを Laravelでは Partialとよぶ
  • FORM部分の PARTIAL化と、PARTIALへの値の渡し方

    • form.blade.php を作成
php.resources/views/articles/form.blade.php
<div class="form-group">
    {!! Form::label('title', 'Title:') !!}
    {!! Form::text('title', null, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
    {!! Form::label('body', 'Body:') !!}
    {!! Form::textarea('body', null, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
    {!! Form::label('published_at', 'Publish On:') !!}
    {!! Form::input('date', 'published_at', $published_at, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
    {!! Form::submit($submitButton, ['class' => 'btn btn-primary form-control']) !!}
</div>
  • published_atの初期値は、$publishd_at 変数が渡されることを想定している
  • また、submitボタンのタイトルも $submitButton変数が渡されることを想定している
  • form.blade.phpを使用するように、edit.blade.phpを修正
php.resources/views/articles/edit.blade.php
~~
    {!! Form::model($article, ['method' => 'PATCH', 'url' => ['articles', $article->id]]) !!}
    @include('articles.form', ['published_at' => $article->published_at->format('Y-m-d'), 'submitButton' => 'Edit Article'])
    {!! Form::close() !!}
~~
  • 最後に、記事の表示画面に、編集ボタンを追加
    • link_toの第1引数にURLを指定
      • actionヘルパー関数を使ってURLを生成
php.resources/views/articles/show.blade.php
@extends('layout')

@section('content')
    <h1>{{ $article->title }}</h1>
    <hr/>

    <article>
        <div class="body">{{ $article->body }}</div>
    </article>
    {!! link_to(action('ArticlesController@edit', [$article->id]), '編集', ['class' => 'btn btn-primary']) !!}
@endsection
  • edit で、 title,bodyが入力済みになっているのはなぜだろう

Part22

記事の削除機能を追加します。
ヘルパー関数の作成にも触れていきます。

学び

  • ArticlesController@destroyへのルートを追加
    • メソッドは deleteにする
    • Route::delete('articles/{id}', 'ArticlesController@destroy');
  • 記事の表示画面に、削除ボタンを追加
php.resources/views/articles/show.blade.php
~~
    {!! link_to(action('ArticlesController@edit', [$article->id]), '編集', ['class' => 'btn btn-primary']) !!}

    <br/>
    <br/>
    {!! Form::open(['method' => 'DELETE', 'url' => ['articles', $article->id] ]) !!}
        {!! Form::submit('削除', ['class' => 'btn btn-danger']) !!}
    {!! Form::close() !!}
@endsection
  • コントローラーに destroy() を実装
php.app/Http/Controllers/ArticlesController.php
~~
    public function destroy($id){
        $article = Article::findOrFail($id);
        $article->delete();
        return redirect('articles');
    }
~~

これで記事の削除機能を実装できました

  • DELETEをリクエストするFormの作成部分をヘルパー関数として切り出してリファクタリングする
  • 新規に helper.php を作成
    • Formタグを生成して返信する delete_form() 関数を追加
php.app/Http/helper.php
<?php
function delete_form($urlParams, $label = '削除')
{
    $form = Form::open(['method' => 'DELETE', 'url' => $urlParams]);
    $form .= Form::submit($label, ['class' => 'btn btn-danger']);
    $form .= Form::close();

    return $form;
}
  • helper.php が Laravel に自動ロードされるように、composer.jsonを修正
    • autoload に "files": ["app/Http/helper.php"]を追加
  • 以下のコマンドで設定を反映
    • composer dump-autoload
  • delete_form() を使うように、ビューファイルを修正
php.resources/views/articles/show.blade.php

// 修正前
{!! Form::open(['method' => 'DELETE', 'url' => ['articles', $article->id] ]) !!}
        {!! Form::submit('削除', ['class' => 'btn btn-danger']) !!}
    {!! Form::close() !!}

// 修正後
    {!! delete_form(['articles', $article->id]) !!}

Part23

フラッシュメッセージの追加をしてみます。

学び

  • ArticlesController.phpのdestroyメソッドを修正
    • \Sessionファサードを使用して、セッションにアクセスしている
    • flashメソッドでフラッシュ情報としてメッセージを追加
    • フラッシュ情報とは次のリクエストだけで有効な一時的なセッション情報のこと
php.app/Http/Controllers/ArticlesController.php
~~
    public function destroy($id){
        $article = Article::findOrFail($id);
        $article->delete();
        \Session::flash('flash_message', '削除しました。');
        return redirect('articles');
    }
~~
  • フラッシュメッセージを表示するように layout.blade.php を修正
    • セッションに ‘flash_message’ をキーに持つ情報があれば、表示するように修正
    • class=”alert alert-success” というのは Bootstrap3 の CSSで、この class を指定すると、正常時のアラートとして div を緑に装飾して表示してくれる
php.resources/views/layout.blade.php
~~
    <div class="container">
        @if (Session::has('flash_message'))
            <div class='alert alert-success'>
                {{ Session::get('flash_message') }}
            </div>
        @endif

        @yield('content')
    </div>
~~

Part24
ナビゲーションメニューの追加を行ってみます。

学び

  • navbar用のビューファイルを作成
    • 今回は上部固定のメニューを作成
php.resources/views/navbar.blade.php
{{-- resouces/views/navbar.blade.php --}}

<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <!-- スマホやタブレットで表示した時のメニューボタン -->
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>

            <!-- ブランド表示 -->
            <a class="navbar-brand" href="/">My Blog</a>
        </div>

        <!-- メニュー -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <!-- 左寄せメニュー -->
            <ul class="nav navbar-nav">
                <li>{!! link_to_route('articles.index', 'Blog') !!}</li>
                <li><a href="/contact">Contact</a></li>
                <li><a href="/about">About</a></li>
            </ul>

            <!-- 右寄せメニュー -->
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Login</a></li>
                <li><a href="#">Register</a></li>

                <!-- ドロップダウンメニュー -->
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">User Name <span class="caret"></span></a>
                    <ul class="dropdown-menu" role="menu">
                        <li><a href="#">Profile</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Logout</a></li>
                    </ul>
                </li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
  • ほぼテンプレ通り使っています
  • レイアウト用ビューファイルにナビゲーションを使うように指定
php.resources/views/layout.blade.php
~~
</head>

<style type="text/css">
<!--
body {
    padding-top: 70px;
}
-->
</style>

<body>

    @include('navbar')
    <div class="container">
~~
  • ナビゲーション用の余白をつくるため、 body に padding を指定
  • <!-- ~~ --> 部分はスタイルシートに対応していないブラウザで

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29