Laravel でDBまわりの定型コードを自動生成してみる

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、私もそれなりに長くプログラミングに携わらせていただいていることもあって、「あっ、これまたか・・・」という場面に遭遇するのが少なからずあったりします。

正直なところ、「いや、このレベルでめんどぐさがってちゃダメだろ・・・💦」と自分でも思うのですが、やはり次の気持ちには勝てないです。

それは・・・・・・・

楽したい!😂

※ なお、お仕事はダルくやってないです!繰り返しの作業が「再放送感」ばかりで新鮮味がないだけです(念のため)

先日も、以下のように「カラムがたくさんある」テーブルのテストデータをつくっていたのですが、

$product = new \App\Models\Product();
$product->category_id = 'xxxxx';
$product->name = 'xxxxx';

// カラムが多いので、ここが延々続く(特にカラム名のコピペがめんどう😫)

$product->save();

そんなときに「もうテーブルは作ってるんだから、すでにある情報を使ってショートカットしたいな😊」となったわけですね。

そこで❗

今回はこの「データベース周りのほぼ決まりきったコード」をクリックだけで自動生成するプログラムをLaravelでつくっていきたいと思います。

自分自身のためにコードをつくる」という、いつもと違った雰囲気を味わっていただけると嬉しいです。m(_ _)m

「実際、楽に作業できるようになりました👍」

開発環境: Laravel 8.x

DBMySQLですが、SQL文が合えばそれ以外でもいけるはずです👍

やりたいこと

まず定形コードを自動生成する手順をまとめます。
以下のとおりです。

  1. データベースを選択(実際に動いてる Laravel プロジェクト以外も選択可)
  2. テーブルを選択する
  3. あらかじめ用意した定形コードが自動生成される

では、やっていきましょう❗

コントローラーをつくる

まずはコントローラーをつくります。
以下のコマンドを実行してください。

php artisan make:controller DatabaseController

すると、ファイルが作成されるので、中身を次のように変更します。

app/Http/Controllers/DatabaseController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Str;

class DatabaseController extends Controller
{
    public function auto_coder(Request $request)
    {
        $target_db = '';
        $target_table = '';
        $table_names = [];
        $columns = [];
        $with_comment = $request->filled('with_comment');

        // データベース名を取得
        $databases = \DB::select('SHOW DATABASES');
        $database_names = array_map(function($database){

            return $database->Database;

        }, $databases);

        try {

            if($request->filled('target_db')) {

                $connection = env('DB_CONNECTION');
                $target_db = $request->target_db;
                config(['database.connections.'. $connection .'.database' => $target_db]); // DB名を上書き
                \DB::reconnect($connection); // DBを再接続

                if($request->filled('target_table')) {

                    // カラム情報を取得
                    $target_table = $request->target_table;
                    $columns = \DB::select('SHOW FULL COLUMNS FROM `'. $target_table .'`');

                }

                // テーブル名を取得
                $tables = \DB::select('SHOW TABLES');
                $table_names = array_map(function($table) use($target_db){

                    return $table->{'Tables_in_'. $target_db};

                }, $tables);

            }

        } catch (\Exception $e) {

            echo 'データベース or テーブル情報が間違っています!';

        }

        return view('database.auto_coder')->with([
            'target_db' => $target_db,
            'target_table' => $target_table,
            'target_table_singular' => Str::singular($target_table),
            'target_table_camel' => Str::singular(Str::camel($target_table)),
            'target_table_studly' => Str::singular(Str::studly($target_table)),
            'database_names' => $database_names,
            'table_names' => $table_names,
            'columns' => $columns,
            'with_comment' => $with_comment
        ]);
    }
}

少しコードが長いので1つずつ見ていきましょう。(自分自身のコードなので冗長な部分が残ってますがご容赦ください😂)

SQL文

コードの中でまず覚えておきたいのが、以下の3つのSQL文です。

  • 全データベースを取得
  • 全テーブルを取得
  • 全カラム(フィールド)情報を取得

通常の開発ではあまり使わないかもしれませんが、これらのSQLは次にようになっています。

  • SHOW DATABASES
  • SHOW TABLES
  • SHOW FULL COLUMNS FROM (テーブル名)

※ 私の場合、SSH接続したときは、たまにコマンドで使うこともあります。

つまり、これらを駆使して定形コードを作成するというわけですね。

データベースの再接続

Laravelでは、.envにセットしたデータベース情報で自動的に接続がされるようになっていますが、各テーブル情報を取得するには再接続しないといけません。

それを行っているのが、以下の部分です。

\DB::reconnect($connection);

ビューをつくる

では、続いてビューです。
以下のファイルを作成してください。

resources/views/database/auto_coder.blade.php

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-5">
    <form method="GET">
        <div class="row">
            @if(!empty($database_names))
                <div class="col-md-6 mb-4">
                    <h1>データベース</h1>
                    <select id="database" class="form-control" name="target_db">
                        <option value="">▼ DB選択</option>
                        @foreach($database_names as $database_name)
                            <option value="{{ $database_name }}"
                                {{ ($database_name === $target_db) ? ' selected' : '' }}
                            >
                                {{ $database_name }}
                            </option>
                        @endforeach
                    </select>
                </div>
            @endif
            @if(!empty($table_names))
                <div class="col-md-6 mb-4">
                    <h1>テーブル</h1>
                    <select id="table" class="form-control" name="target_table">
                        <option value="">▼ Table選択</option>
                        @foreach($table_names as $table_name)
                            <option value="{{ $table_name }}"
                                {{ ($table_name === $target_table) ? ' selected' : '' }}
                            >
                                {{ $table_name }}
                            </option>
                        @endforeach
                    </select>
                    <label>
                        <input type="checkbox" name="with_comment" value="true"> コメントを付ける
                    </label>
                </div>
            @endif
            @if(!empty($columns))
                <div class="col-md-12">
                    <h1>コード</h1>
                </div>
                <div class="col-md-6 mb-4">
                    <h2>Create</h2>
                    <div class="bg-light p-3">
                    ${{ $target_table_singular }} = new \App\Models\{{ $target_table_studly }}();<br>
                    @foreach($columns as $column)
                        ${{ $target_table_singular }}->{{ $column->Field }} = $request->{{ $column->Field }};
                        @if(!empty($column->Comment) && $with_comment === true)
                            // {{ $column->Comment }}
                        @endif<br>
                    @endforeach
                    ${{ $target_table_singular }}->save();
                    </div>
                </div>
                <div class="col-md-6 mb-4">
                    <h2>Array in PHP</h2>
                    <div class="bg-light p-3">
                    ${{ $target_table_singular }} = [<br>
                    @foreach($columns as $column)
                        &nbsp;&nbsp;&nbsp;&nbsp;
                        '{{ $column->Field }}' => $request->{{ $column->Field }},
                        @if(!empty($column->Comment) && $with_comment === true)
                            // {{ $column->Comment }}
                        @endif
                        <br>
                    @endforeach
                    ]
                    </div>
                </div>
                <div class="col-md-6 mb-4">
                    <h2>Object in JavaScript</h2>
                    <div class="bg-light p-3">
                    const {{ $target_table_camel }} = {
                    <br>
                    @foreach($columns as $column)
                        &nbsp;&nbsp;&nbsp;&nbsp;
                        {{ $column->Field }}: {{ $target_table_camel }}.{{ $column->Field }},
                        @if(!empty($column->Comment) && $with_comment === true)
                            // {{ $column->Comment }}
                        @endif
                        <br>
                    @endforeach
                    }
                    </div>
                </div>
                <div class="col-md-6 mb-4">
                    <h2>Inputs</h2>
                    <div class="bg-light p-3">
                        @foreach($columns as $column)
                            &lt;div class="mb-3"&gt;<br>
                            &nbsp;&nbsp;
                            &lt;label&gt;{{ $column->Comment ?: $column->Field }}&lt;/label&gt;<br>
                            &nbsp;&nbsp;
                            &lt;input class="form-control" name="{{ $column->Field }}"&gt;<br>
                            &lt;/div&gt;<br><br>
                        @endforeach
                    </div>
                </div>
            @endif
            <div class="col-md-12">
                <button type="submit" class="btn btn-primary btn-lg mt-3">送信する</button>
                <a href="./auto_coder" class="btn btn-link">クリア</a>
            </div>
        </div>
    </form>
</div>
<script>

    window.onload = () => {

        document.querySelector('#database').addEventListener('change', () => {

            const table = document.querySelector('#table');

            if(table) {

                table.innerHTML = '';

            }

        });

    };

</script>
</body>
</html>

中身としては、先ほど取得した各データベース情報を@foreach() 〜 @endforeach でループさせながら表示をしているだけです。

ちなみに

なお、.envSESSION_DRIVERdatabaseになっていると、sessionsテーブルが必須になるため、他のデータベースではエラーになります。

そのため、ここはfileをセットしておいてください。

.env

SESSION_DRIVER=file

テストしてみる

ということで、今回は短いですが実際にテストしてみましょう。定形コードを作成するのは、別プロジェクト「laravel7x」の「cards」というテーブルです。

では、「https://example.com/database/auto_coder」にブラウザでアクセスしてください。

するとデータベースを選択できるので、「laravel7x」を選択します。

そのまま送信ボタンをクリックします。

すると、laravel7xのテーブルが選択できるようになるので、「cards」テーブルを選択し、再度送信します。

(コメントはDBのコメントがある場合に表示が増えます)

すると・・・・・・

はい❗

いくつか定形コードが表示されました。
実際のコードは次のとおりです。

Create

$card = new \App\Models\Card();
$card->id = $request->id;
$card->url = $request->url; // URL
$card->title = $request->title; // タイトル
$card->thumbnail_url = $request->thumbnail_url; // サムネイルURL
$card->description = $request->description; // ページ概要
$card->created_at = $request->created_at;
$card->updated_at = $request->updated_at;
$card->save();

Array in PHP

$card = [
    'id' => $request->id,
    'url' => $request->url, // URL
    'title' => $request->title, // タイトル
    'thumbnail_url' => $request->thumbnail_url, // サムネイルURL
    'description' => $request->description, // ページ概要
    'created_at' => $request->created_at,
    'updated_at' => $request->updated_at,
]

Object in JavaScript

const card = {
     id: card.id,
     url: card.url, // URL
     title: card.title, // タイトル
     thumbnail_url: card.thumbnail_url, // サムネイルURL
     description: card.description, // ページ概要
     created_at: card.created_at,
     updated_at: card.updated_at,
}

Inputs

<div class="mb-3">
   <label>id</label>
   <input class="form-control" name="id">
</div>

<div class="mb-3">
   <label>URL</label>
   <input class="form-control" name="url">
</div>

<div class="mb-3">
   <label>タイトル</label>
   <input class="form-control" name="title">
</div>

<div class="mb-3">
   <label>サムネイルURL</label>
   <input class="form-control" name="thumbnail_url">
</div>

<div class="mb-3">
   <label>ページ概要</label>
   <input class="form-control" name="description">
</div>

<div class="mb-3">
   <label>created_at</label>
   <input class="form-control" name="created_at">
</div>

<div class="mb-3">
   <label>updated_at</label>
   <input class="form-control" name="updated_at">
</div>

成功です😊✨
もちろん、コード内容はauto_coder.blade.php内で自由に変更できますよ👍

ということで、これからはマウスでクリックするだけでDB用の定形コードをつくることができます。

なお、通常idcreated_atupdated_atは直接使わないのですが、例外も考慮して残しました。気になる場合はin_arrayなどでフィルターをかけるといいでしょう。

【⚠ただし!】
このコードは開発専用ですので、本番環境では使わないようがいいです。バリデーションはつけてないですし、何よりDBが危険にさらされます。そのため、ローカル環境のみで使うようにしてください。(ダメ、ゼッタイ❗)

企業様へのご提案

今回見ていただいたように、「何度もしないといけない似た作業」はアイデアしだいで定形コードをつくってショートカットすることができます。

もし、会社独自の仕様で定形コードや、定形文を作りたい場合はお力になれると思いますので、ぜひお問い合わせからご連絡ください。

どうぞよろしくお願いいたします。m(_ _)m

おわりに

ということで、今回はいつもと違い「自分自身のために」プログラムを作ってみました。

ただ、個人的な意見ですが「自分自身のために」何かをつくるというのはとてもいい経験になると思います。

なぜなら、「実際に自分で使うから」です。

自分で使うということは、「うーん、ここもっとこうしてほしい」という部分に気がつくので、ユーザーの気持ちを理解することにつながると考えています。

つまり、自分自身を客観的にみることができるというわけですね。

すると、自分がつくる作品の傾向がわかるので、結果的により質の高いものがつくれるようになるんじゃないでしょうか。(と、信じてます 🙏✨)

ぜひ皆さんもたまに自分のためにコードを書いてみてくださいね。

ではでは〜❗


「ここに来てなぜか肌が
きれいになりました
(ホントになぜ❗❓)」

開発のご依頼お待ちしております 😊✨ お問い合わせ
また、こちらもお待ちしております。
  • 実案件の開発サポート: 詳細
  • ツイッターのフォロー: 詳細
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly