Ikeda's Blog

LaravelでDBから取得した主キーがおかしい

前提条件

以下のような設定でテーブルを作成していた。
先に言ってしまうと、主キーが文字列なことが問題点。

Schema::create('members', function (Blueprint $table) {
    $table->string('id', 50)->primary();
    $table->text('name');
    $table->timestamps();
    $table->softDeletes();
});

データ内容

id name
a.ikeda Ikeda A
b.ikeda Ikeda B

データを取得し、内容を表示すると……

dump(Member::all()->toArray());
array:2 [▼
  0 => array:5 [▼
    "id" => 0
    "memo" => "Ikeda A"
  ]
  1 => array:5 [▼
    "id" => 0
    "memo" => "Ikeda B"
  ]
]

idが「0」になってしまう。

原因

ドキュメントの Eloquent ORM > Eloquentの準備にある、「主キー」

さらに、Eloquentは、主キーが増分整数値であることも想定しています。これは、Eloquentが主キーを自動的に整数にキャストすることを意味します。

そのまま書いてありました。主キーを整数にキャストしてしまうため、文字列だけ入れていると「0」になってしまうのでした。
そして、解決方法も同じように書いてあります。

非インクリメントまたは非数値の主キーを使用する場合は、モデルにpublicの$incrementingプロパティを定義し、falseをセットする必要があります。

モデルの主キーが整数でない場合は、モデルにprotectedな$keyTypeプロパティを定義する必要があります。このプロパティの値はstringにする必要があります。

の2箇所です。

対策

app\Models\*****.phpに、以下の記述を追加することで解決します。

<?php

class Member extends Model
{
    public $incrementing = false; // モデルのIDを自動増分するか
    protected $keyType = 'string'; // IDのデータ型

    // 以下省略
}

LaravelでFormの入力データから改行が消える件

はじめに

Laravelで、Formから入力したデータを受け取った時に奇妙な動きをしていた。

テキストエリアで、文頭や文末に入れた改行が消えてしまう。

入力値そのままにDBへ入れようとしていた時に発見。
原因の調査と、対策についてをまとめておく。

原因

まあ、ちゃんとドキュメントに記述がありました。
入力のトリムと正規化

リクエストに応じてすべての受信文字列フィールドを自動的にトリミングし、空の文字列フィールドをnullに変換します。

なるほど。

対策

A. 対象外の項目を指定する

\App\Http\Middleware\TrimStrings::classの中に、$except という変数がある。
この中に、トリミングしたくない入力枠のnameを記入する。

B. ミドルウェアを無効にする

すべての入力内容で、トリミングしないようにしてしまう方法。
laravel/app/Http/Kernel.php にある、\App\Http\Middleware\TrimStrings::classをコメントアウトしてしまう。

※空の時にnullになるのが困る場合は、\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::classも。

Laravelに認証機能を導入(Laravel Fortify)

概要

とあるレンタルサーバに、Laravelで作ったWEBアプリをリリースしようとしたら、npmが使えない事態が発生。
Laravel Jetstreamを使っていたので、構築段階でコケる状態になってしまった。
代替手段として、Laravel Fortifyを使ったので、導入方法の備忘録。

コマンド実行

composer require laravel/fortify
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
php artisan migrate

config

config/app.phpに、以下を追加します。

App\Providers\FortifyServiceProvider::class,

登録、ログインページの定義

app/Providers/FortifyServiceProvider.phpを編集します。

<?php

// ... 省略 ...

class FortifyServiceProvider extends ServiceProvider
{
    // ... 省略 ...

    public function boot()
    {
        // ... 省略 ...

        // 登録ページは、resources/views/auth/register.blade.phpを表示する
        Fortify::registerView(function () {
            return view('auth.register');
        });

        // ログインページは、resources/views/auth/login.blade.phpを表示する
        Fortify::loginView(function () {
            return view('auth.login');
        });
    }
}

view

登録ページ

登録時に必要なのは、「名前(name)」「メールアドレス(email)」「パスワード(password)」「パスワード再入力(password_confirmation)」の4項目です。
それさえあれば良いので、以下だけでも動きます。

<form method="post" action="{{route('register')}}">
    @csrf
    name:<input type="text" name="name"><br />
    mail:<input type="text" name="email"><br />
    pass:<input type="password" name="password"><br />
    pass:<input type="password" name="password_confirmation"><br />
    <button type="submit">登録</button>
</form>

ログインページ

こちらは、「メールアドレス(email)」「パスワード(password)」があれば良いので、更にシンプル。

<form method="post" action="{{route('login')}}">
    @csrf
    mail:<input type="text" name="email"><br />
    pass:<input type="password" name="password"><br />
    <button type="submit">ログイン</button>
</form>

動作確認

http://(ドメイン)/registerから登録し、
http://(ドメイン)/loginからログインできるかを確認。

備考:ログイン後の遷移先を指定

app/Providers/RouteServiceProvider.phpにある、HOMEの値を変更すればOK。

【Laravel】バリデーションの前に、パラメータを操作する方法

やりたいこと

Laravelのバリデーションを行う前に、入力値に対して処理を行いたい。
例:半角英数字を入力してもらいたい項目だが、全角で入力された場合でも、半角に変換して受け入れてあげたい。

Request

prepareForValidationを利用することで、実現可能です。

<?php

// ... 省略 ...

class HogeRequest extends FormRequest
{
    public function rules()
    {
        return [
            //           必須        英数字     50文字
            'key' => ['required', 'alpha_num', 'max:50'],
        ];
    }

    public function prepareForValidation() {
        $this->merge([
            'key' => mb_convert_kana($this->key, 'a'),
        ]);
    }
}

Laravelでブログサイトを作る 21. 管理画面・記事のプレビュー機能

目的

記事編集途中に、プレビュー表示して確認したい。

ルーティング

routes/web.php
プレビュー用のルーティングを追加します。更新と同様に、入力値をPOST送信する予定です。

// ...省略...

Route::group(['prefix' => 'admin', 'middleware' => 'auth', 'namespace' => 'App\\Http\\Controllers\\'], function() {
    // ...省略...

    Route::post('articlePreview', 'ArticleController@preview')->name('admin_article_preview'); // プレビュー

    // ...省略...
});

テンプレート

resources/views/admin/articleEdit.blade.php
プレビューボタンを設置します。
更新する時と同じく、入力内容をsubmitします。

{{-- 省略 --}}
<form method="post">
    {{-- 省略 --}}

    <input type="button" id="preview" value="プレビュー" />
    <input type="button" id="update" value="{{ isset($articleData->id) ? '更新' : '登録' }}" />
</form>
{{-- 省略 --}}

<script>
    $(document)
        .on('click', '#preview', function(){
            $(this).parents('form').attr('action', '{{ route('admin_article_preview') }}').attr('target', '_blank');
            $(this).parents('form').submit();
        })
        .on('click', '#update', function(){
            $(this).parents('form').attr('action', '{{ route('admin_article_update') }}').attr('target', '');
            $(this).parents('form').submit();
        })
    ;
    </script>

Controller

app/Http/Controllers/ArticleController.php

記事詳細表示の共通化

フロントの記事詳細画面と、プレビュー表示で共通化できる部分が多いので、先に対応します。
(記事の詳細表示するcontent()の一部分を抜き出す。抜き出した箇所にはreturn $this->articleView($articleData, $articleChildrenData);を記述する)

    /**
     * 記事1件分のページ表示(フロントの記事詳細と、管理画面のプレビュー機能から)
     * @param $articleData
     * @param $articleChildrenData
     * @return mixed
     */
    private function articleView($articleData, $articleChildrenData) {
        // 親カテゴリを取得
        $parentCategoryList = $this->parentCategory->orderBy('number', 'asc')->orderBy('id', 'asc')->get();

        // 子カテゴリは、キーを親カテゴリIDにした連想配列にする
        $data = $this->childCategory->orderBy('number', 'asc')->orderBy('id', 'asc')->get();
        $childCategoryList = [];
        foreach ($data as $item) {
            $childCategoryList[$item->parent][] = $item;
        }

        // 目次対応
        $articleData->body = $this->makeIndex($articleData->body);

        return view('front.article', [
            'articleData' => $articleData,
            'articleChildrenData' => $articleChildrenData,
            'parentCategoryList' => $parentCategoryList,
            'childCategoryList' => $childCategoryList,
            'parentNum' => $this->getParentArticleNum(),
            'childNum' => $this->getChildArticleNum(),
            'newArticleData' => $this->getNewArticleList(),
        ]);
    }

プレビュー表示

内容はシンプルで、

  1. 入力値を取得して変数に格納する
  2. 本来はDBから取得する記事データを、入力値にする
    • JOINで取得する親カテゴリ名を別途取得してセットする
  3. 記事と関連付けられている子カテゴリは、入力値から子カテゴリのリストとして取得する
    という感じです。
    これらの情報を、先程の共通化メソッドに渡してやれば、ページ表示できます。
    /**
     * プレビュー表示更新
     *
     * @param Request $request
     * @return mixed
     */
    public function preview(Request $request)
    {
        // 入力値取得
        $id = $request['id'];
        $tiitle = $request['title'];
        $body = $request['body'];
        $parent = $request['parent'];
        $childList = $request['child'];
        $open = $request['open'];
        $private = $request['private'];
        $description = $request['description'];

        // 記事取得
        $articleData = new Article();
        $articleData->title = $tiitle;
        $articleData->body = $body;
        $articleData->parent = $parent;
        $articleData->open = $open;
        $articleData->private = $private;
        $articleData->description = $description;

        // 親カテゴリ名を保持させる
        $parentData = $this->parentCategory->where('id', $parent)->first();
        $articleData->name = $parentData->name;

        // 記事と関連付けられている子カテゴリの情報を取得
        $articleChildrenData = null;
        if (is_array($childList)) {
            $articleChildrenData = $this->childCategory->whereIn('id', array_keys($childList))->get();
        }

        return $this->articleView($articleData, $articleChildrenData);
    }