小規模なLaravel開発の初期対応

Laravel
  • URLをコピーしました!

目次

要求定義から要件定義(機能要件)などがまとまりました。
続いて、方式設計としてフレームワークはLaravelが選ばれました。

画面設計・データ設計が完了しました。画面設計とデータ設計は、どちらもLaravelを使うことを少し意識してもらっています。1

ここからLaravel開発の初期対応をおこなっていくことになることが多いと思います。金額が安い案件(数百万円くらい)であれば、詳細設計を省いて個々の機能担当者がコーディングしながら考えていくことも多いのではないでしょうか。

今回は、わたしが個人的にベストプラクティスとしてやっている小規模なLaravel開発の初期対応の流れをまとめました。

目次

前提

まず前提ですが、インフラ担当者がどこかのgitリポジトリにDockerなどでサーバー、Laravel8、MySQLや開発に必要なphpMyAdminとかMailhogの初期設定まで完了してくれているところからスタートします。

LaravelにはDocker環境である Laravel Sail が準備されているので、これを使うのも良いと思います。くわしくはこちら
https://readouble.com/laravel/8.x/ja/installation.html

最初の確認

  • データ設計書やER図をじっくり読み込みます。
  • 画面設計書やHTMLのコーディング内容から、どのデータがどこに表示されるのかを確認します。2
  • 画面設計書から、ルーティング(URLのことです。Laravelでは routes で定義します)を確認します。ルーティングの無い画面設計書の場合は、基本的には以下のように考えましょう!

初期対応開始!

1. Modelとmigrationファイルを作る

データ設計書から、まずはmigrationファイルを作成していきます。

公式説明 Eloquentの準備
https://readouble.com/laravel/8.x/ja/eloquent.html

php artisan make:model User --all

ファイル作成の順番はもうわかっていますね?4

php artisan make コマンドにはいろいろなコマンドがありますが、個人的には、make:model --all をおすすめします!これを実行すると、migrationファイル、Model、Factory、Seeder、Controller、Policyが作成されます。これらは作っておくことに意義があると思っています!
なので、新しいテーブルを新設するときは、必ず --all オプションを使っていきましょう!

2. migrationファイルを埋める

migrationファイルの中身を埋めていきます。MySQLを使う場合は、きっちりコメント5入りで書くことをおすすめします!

  • 特にステータスが何を意味するのか?
  • 整合性を取るための情報は書いていきましょう!
  • なぜ正規化しないのか、などを書いていきましょう。

ツチノコテクノロジーでは、開発が開始したあとのデータベース資料は、SchemaSpyを参照することが多くなります。そこで意味がわかるようにコメントの追加をお願いします!

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\User;

class CreateContactsTable extends Migration
{

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('contacts', function (Blueprint $table) {
            $table->id();
            $table->foreignIdFor(User::class)->nullable();
            $table->string('name')->comment('名前:ログインユーザーの場合には自動設定');
            $table->string('tel')->comment('電話番号:ログインユーザーの場合には自動設定');
            $table->string('email')->comment('メールアドレス:ログインユーザーの場合には自動設定');
            $table->text('body')->comment('お問い合わせ本文');
            $table->boolean('responsed')->default(false)->comment('運営者が返信したか');
            $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
        });
        DB::statement("ALTER TABLE contacts COMMENT 'お問い合わせ履歴'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('contacts');
    }
}

3. Modelの初期設定をする

Modelの初期設定をしていきましょう!

日付のキャストを定義する

デフォルトでは、Eloquentは created_atupdated_at カラムは Carbon のインスタンスへキャストしてくれます。

公式説明 Eloquentミューテタ/キャスト
https://readouble.com/laravel/8.x/ja/eloquent-mutators.html

それ以外の timestamp 型のデータは Carbon にそろえる設定をしましょう!

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 日付を変形する属性
     *
     * @var array
     */
    protected $dates = [
        'seen_at',
        'created_at',
        'updated_at',
    ];
}

リレーションを定義する

外部キーにしたがって、結合の定義をしていきましょう!

公式説明 Eloquent リレーション
https://readouble.com/laravel/8.x/ja/eloquent-relationships.html

4. FactoryとSeederをきっちり作成する

一番最初にやることは、想定されるデータを多めに入れておくことです。
Factoryでは適当なデータを返さずに、きちんと整合性のとれたデータを返すようにしましょう。

わたしがFactoryで多用するのは、User::all()->random()->id です。これで外部キーもばっちり適当に作れます。

Seederデータはどこまで作るのか?

Seederデータ作成は、データ設計理解を助け、UnitTestを助け、開発を助けるための要(かなめ)です。以下の方針で、数日かけてきちんと作っていきましょう!

  • Seederは何度実行してもいいように upsert を使いましょう!PrimaryKeyをしっかり意識しましょう!
  • マスターデータは完璧に近づける。ほぼ完ぺきにする。
    • このSeederはのちほど本番リリース時に使用してもよいと思えるくらい完璧にしましょう。
  • トランザクショナルなデータは、100件以上は作りましょう!特にページングがある画面に表示する場合は、500件は欲しいです!Factoryをうまく使いましょう!
  • そして、データの状態を意識して、できるだけ全パターン作成します。これはのちのち検索テストをするときに、検索条件に適合するデータが無いことが無いようにしたいです。
    • このとき、絶対ありえないデータを作らないことがポイントです!例えば、新規会員登録直後で、メール認証が終わっていないユーザーはマイページを表示できないシステムなのに、マイページのいくつかの機能でデータを作成している…とか、こういうのはダメです!
    • データ設計を理解して、正確なテストデータを作成するように心がけましょう!ここがうまくやらないと、あとからテストしたときに「なんだこの変なデータ?」となって、時間をロスする可能性があります。

※超注意※ 現行システムのデータをSeederデータにする場合

上長の許可を得て、あるいは顧客の許可を得て、現行システムのデータをSeederで使うことで時間短縮しよう!となりました。このとき絶対きちんと対応するべきことは、個人情報はすべてマスクすることです!

名前、住所、電話番号、メールアドレス、LINEなどSNSのIDを残さないこと。万が一にも、ちょっとした出来心で連絡が取れるようにしないこと!これは、Factoryをうまく使ってその部分だけはFakeデータとして設定できるようにしましょう!

5. routesを設計書どおりに設定する

migration、Model、Factory、Seederまで作成が完了しました。続いて、routesを定義していきます。ここはもう決まった内容をペタペタやっていくだけです。

Controllerはどうしようかな?と考えてしまう方も多いと思いますが、データ中心に考えるとやりやすいと思います。1で実行した php artisan make:model User --all のおかげで、Controllerのガワはできていますので、データに対応したURLを紐づけていくようなイメージで設定していきましょう!

公式説明 ルーティング
https://readouble.com/laravel/8.x/ja/routing.html

use App\Http\Controllers\ContactController;
use App\Http\Controllers\ProfileController;

Auth::routes([
    'reset'    => true,
    'register' => true
]);

Route::get('/contact', [ContactController::class, 'contact']);
Route::post('/contact/confirm', [ContactController::class, 'confirm']);
Route::post('/contact/complete', [ContactController::class, 'complete']);

Route::group(['middleware' => 'auth'], function () {
    Route::get('/profile', [ProfileController::class, 'show']);
    Route::post('/profile/confirm', [ProfileController::class, 'confirm']);
    Route::post('/profile/complete', [ProfileController::class, 'complete']);
    Route::get('/contacts', [ContactController::class, 'index']);
    Route::get('/contacts/{contact}', [ContactController::class, 'show']);

個人的には、ルートモデル結合は、積極的に取り入れていきたいです。
上の例では Route::get('/contacts/{contact}', [ContactController::class, 'show']); のところです。公式説明の「ルートモデル結合」を読んでみてください。 {contact} には id を指定します。Controllerの指定した関数(ここでは show の引数に、 Contact が入っている状態にすることができます。

6. HTTPテストを作成する

この段階で、すべてのルーティングに対して、HTTPテストを作成します。

公式説明 テストの準備
https://readouble.com/laravel/8.x/ja/testing.html

php artisan make:test ContactControllerTest # tests/Feature フォルダに作成される
php artisan make:test ContactTest --unit # tests/Unit フォルダに作成される

基本的にはこんな感じの簡単なテストになるでしょう。
初期設定の TestCase クラスを、 use Tests/TestCase にすることを忘れずに!

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ContactControllerTest extends TestCase
{
    use RefreshDatabase;

    /**
     * testContact
     *
     * @return void
     */
    public function testContact()
    {
        // $this->seed();

        // index
        $response = $this->get('/contact');
        $response->assertStatus(200);

        // 戻るテスト
        $response = $this->post('/contact/confirm',[
            'action'  => 'back'
        ]);

        $response->assertStatus(302);
        $response->assertRedirect('contact');

        // validation テスト 必須テスト
        $response = $this->post('/contact/confirm',[]);

        $response->assertStatus(302);
        $response->assertInvalid([
            'name'  => 'お名前 は必須です',
            'email' => 'メールアドレス は必須です',
            'tel'   => '電話番号 は必須です',
            'body' => 'お問い合わせ内容 は必須です',
        ]);

        // validation テスト 入力値の誤りテスト
        $response = $this->post('/contact/confirm',[
            'name'  => 'サンプル名前',
            'email' => 'thankyou', // メールアドレス構文エラー
            'tel'   => '0120333333', // 大文字は入力エラー
            'body' => 'お問い合わせ',
        ]);

        $response->assertStatus(302);
        $response->assertInvalid([
            'email' => '利用可能なメールアドレスをご入力ください',
            'tel'   => '半角数字のみをご入力ください(ハイフンなし)',
        ]);

        // validation 合格
        $response = $this->post('/contact/confirm',[
            'name'  => 'サンプル名前',
            'email' => 'tt@tutinoko.tech',
            'tel'   => '00011112222',
            'body' => 'お問い合わせ',
        ]);

        $response->assertStatus(200);
        $response->assertValid(['name', 'email', 'tel', 'body']);

        // validation 合格
        $response = $this->post('/contact/complete',[
            'name'  => 'サンプル名前',
            'email' => 'tt@tutinoko.tech',
            'tel'   => '00011112222',
            'body' => 'お問い合わせ',
        ]);

        $response->assertStatus(200);
        $response->assertValid(['name', 'email', 'tel', 'body']);
    }

$response->assertXXX の種類については、こちら

公式説明 HTTPテスト
https://readouble.com/laravel/8.x/ja/http-tests.html

Laravel は利便性を良くするため、テストの実行時にCSRFミドルウェアを自動で無効にします!かしこいですね!(^^)/

じっくり派のかたは、このとき Validation についても記載してしまうことをおすすめします!

逆にここはあっさり進みたいかたは、POSTのルーティングは無視して、ひとまずGETのルーティングに対して HTTP ステータス 200 の確認テストだけ作りましょう!

上の例では、confirmcomplete では confirm の方でだけ入力値のチェックをしていますが、同じ FormRequest を指定することで、片側だけのチェックでOKです!

このとき、UnitTestをすべて実行してエラーになることを確認しましょう!

php artisan test

7. FormRequestファイルを作成する

Route::post を定義したところや、GETパラメーター6を受け取るときに利用するFormRequestを作成していきます。

Validationの内容やリダイレクト先(Validationエラーになった時に戻す画面)も記載していきます。

公式説明 フォームリクエストバリデーション
https://readouble.com/laravel/8.x/ja/validation.html

php artisan make:request ContactPostRequest

※FormRequestを作成した際には、必ず、authorize()return true になるように定義してください!ここを未定義のままだと、このルートにアクセスできなくなってしまいます( ;∀;)

/**
 * ユーザーがこのリクエストの権限を持っているか判断
 *
 * @return bool
 */
public function authorize()
{
    return true;
}

共通のvaliadtionを設定する場合は resources/lang/ja/validation.php を設定することになります。このとき、 config/app.phplocale を日本に定義します。timezone も気にしましょう!

'timezone' => 'Asia/Tokyo',
'locale' => 'ja'

AuthのパスワードリセットページのValidationメッセージの言語切り替えはこちらになります。

公式説明(日本語翻訳) password.php 言語ファイル
https://readouble.com/laravel/8.x/ja/passwords-php.html

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | パスワードリセット言語行
    |--------------------------------------------------------------------------
    |
    | 以下の言語行は既存のパスワードを無効にしたい場合に、無効なトークンや
    | 新しいパスワードが入力された場合のように、パスワードの更新に失敗した
    | 理由を示すデフォルトの文言です。
    |
    */

    'reset' => 'パスワードをリセットしました。',
    'sent' => 'パスワードリセットメールを送信しました。',
    'throttled' => 'しばらく再試行はお待ちください。',
    'token' => 'このパスワードリセットトークンは無効です。',
    'user' => "メールアドレスに一致するユーザーは存在していません。",

];

8. routesにしたがって、Controllerの関数を定義する

ルーティングで定義した関数を、Controllerに作成していきます。このとき、Postのところで受け取る Request は、先ほど作成した FormRequest を使用します。指定する FormRequest のクラスが足りないときはここで作成しましょう。Request クラスを使わないようにします。ルートモデル結合もここできちんと指定します。

ここでは、関数を定義するだけで、まだ中身は記載しません。

return view('profile.show'); などの表示するbladeかまたは return redirect('/profile')->with('flash_message', 'プロフィールを更新しました。'); などのリダイレクトだけを定義しておきます。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use App\Http\Requests\ContactPostRequest;

class ContactController extends Controller
{

    /**
     * お問い合わせ
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function contact()
    {
        return view('contacts.contact', []);
    }

    /**
     * お問い合わせ確認
     *
     * @param ContactPostRequest $request
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function confirm(ContactPostRequest $request)
    {
        return view('contacts.confirm', []);
    }

    /**
     * お問い合わせ完了
     *
     * @param ContactPostRequest $request
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function complete(ContactPostRequest $request)
    {
        // ログインユーザーの場合は、お問い合わせ履歴画面へ
        if (Auth::check()) return redirect('/contacts')->with('flash_message', 'お問い合わせを送信しました');
        return view('contacts.complete', []);
    }
}

9. bladeファイルをとりあえず作成して、ブラウザで表示できるようにする

HTMLコーディングされた内容をペタッと貼り付けます。まだ分岐とかループとかは記載せず、HTMLを素直に張り付けていきましょう。
layouts や components など決めてしまいましょう!

公式説明 Bladeテンプレート
https://readouble.com/laravel/8.x/ja/blade.html

assetroute などヘルパーを使っていきます。これで一応画面がきれいに表示され、画面遷移ができるようにします。

assetを使用
<link rel="stylesheet" href="{{ asset('/css/bootstrap.min.css') }}">

urlを使用
<a href="{{ url('/posts/'.$post->id ) }}">{{ $post->title }}</a>

routeを使用
<a href="{{ route('post.show', ['post' => $post->id]) }}">{{ $post->title }}</a>

コンポーネントの部分をぜひ読んでください!パーツについての理解が深まると思います!全画面に表示されるグローバルメニューにログインユーザーの名前を出すとか、そういうときに便利です!

php artisan make:component Header

この時点で Feature の UnitTest は、validationの作り込みをしていないところ以外は、基本的にはグリーンになるはずです。UnitTestを実行してみましょう!

php artisan test

10. 初期対応完了!開発開始!

いままでさんざん開発してきたじゃないかーーい!とツッコミしてくださったみなさんありがとうございます。ここまでが Laravel 開発の初期対応になります。

なぜここまでが初期対応かというと、ここまでは設計方針に従って設定していくだけで、まったく考える必要が無い、アーキテクチャがLaravelというフレームワークで定まっている部分だからです。ここから先が本当の意味での「開発」になります。

以下、気をつけて開発していきましょう!

  • 命名規則(クラス名、関数名、変数名)
  • PHPDocやコメント
  • Controller内でデータをごにょごにょと作成しないようにしていきましょう!
    • データ作成は、ModelやServices(Utility)などで行って、tests/Unit のUnitTestを増やしていきましょう!
  • アクセス禁止(認可)処理は FormRequest か Gate か Policy で記載していきます!

Footnotes

  1. たとえば、ユーザーの情報は、 users という複数形の命名にしてもらいます。これは、Laravelがそういう複数形名をサポートしているからです。
    画面設計でURLやエンドポイントは、 /users とか /admin/users とかにしてもらうことで、設計をわかりやすくそろえることで、開発しやすくしています。
  2. HTMLコーディングが別チームの場合、データ設計で使われている名前とCSSや input name='' で使われている名前が違う可能性があります。もし相談できるのであれば、この時点で相談しておくとよいと思います。
  3. 画面設計書では、「表示するためのURL」が定義されることが多いです。例えば、新規会員登録の確認画面は /register/confirm となっていたりします。あとで設定するルーティングも、基本的には表示する画面をベースに考えていきますので覚えておきましょう!
  4. migrationファイルは、作成された時間をファイル名にもち、その昇順でデータ定義の実行順序が決まります。データ設計書を確認して、外部キーが入っていないテーブルから作成し、外部キーが入っている場合は、事前にそのテーブルが作成済みであることを確認しましょう!そうじゃないとmigrate実行時にエラーになります!
  5. コメントは最大 1024 文字
  6. URLの http://xxx.jp/search?keyword=xxx&year=2022&viewable=1 とかで受け取るパラメーターのことです

ツチノコテクノロジーでは一緒に働く仲間を募集しています!

完全リモートで働きたい方へ!

詳しくは以下をご覧ください。

ツチノコテクノロジー採用サイト

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ツチノコテックアカデミアの記事は、社内で誰かが質問してくれたことに回答したときに、ついでに記載しています!(^^)/
みんなの悩みを共有すれば、きっと誰かの役に立つと信じて更新しています!

目次