A Laravel(DB関連 Eloquent) - user000422/0 GitHub Wiki

概要

Active RecordライクなORMです。 トランザクションを忘れるな。 スコープを積極的に使うこと。複雑なクエリをModelに隠蔽できる。 レコードの更新は「save」ではなく「create(update)」を使用する。簡潔だから。 Laravelでは、Daoクラスよりもリポジトリクラスとしてリソース(DBデータに限らない)操作部分をまとめる例が多いです。

マイグレーション

データベースのバージョン管理機能。 テーブル名は複数形であること。(Laravel公式) カラム名はスネークケースかつ基本は単数形であること。(Laravel公式) マイグレーションスクリプトファイルの場所 database/migrations 手順 : スクリプトファイル作成 -> スクリプトファイル編集 -> マイグレーション

# artisan マイグレーションスクリプトファイルの作成(テーブル単位)
# ファイル名規則 : create_{作成したいテーブル名}_table
php artisan make:migration create_samples_table

# マイグレーション + MySQL に必要なパッケージのインストール
dnf install php-pdo
dnf install php-mysql

# artisan マイグレーション実行
php artisan migrate
php artisan migrate:fresh # 【危険なため末尾を加工】テーブルのレコードを全て削除しマイグレーション
php artisan migrate:refreshhh # 【危険なため末尾を加工】テーブルのレコードを全て削除しマイグレーション

# artisan make:model モデルを作成
php artisan make:model SampleTable

# artisan make:seeder シーダーを作成
php artisan make:seeder UserSeeder

# artisan db:seed シーダーを実行
php artisan db:seed # 一括実行(DatabaseSeederに記載したシーダー)
php artisan db:seed --class=UserSeeder # 個別指定
// マイグレーションスクリプトファイル database/migrations/~

// up : テーブル作成
public function up(): void
{
    Schema::create('samples', function (Blueprint $table) {
        // Nullはデフォルトで許容しない
        $table->increments('id'); // 自動採番
        $table->bigIncrements('id');
        $table->integer('age');     // integer
        $table->string('password'); // varchar

        $table->integer('sample')->nullable(); // NULL許容
        $table->integer('is_active')->default(1); // デフォルト値
        $table->timestamps(); // カラムが2つ作成「created_at」「updated_at」
    }
}

// down : テーブル削除
public function down(): void
{
        // dropColumn('カラム名')だけでよい
        $table->dropColumn('age');
}

Eloquent エロクワント

■Model モデルクラスの命名規則(Laravel公式) : テーブル名は複数形。モデルクラス名は単数系。 他の名前を明示的に指定しない限り、クラス名を複数形の「スネークケース」にしたものが、テーブル名として使用される。 統一するために下記のように各モデルで定義してもいいと思う。

class User extends Model
{
    use HasFactory;

    // 【推奨】テーブル名の設定
    protected $table = 'users';

    // 【推奨】主キーの設定(ここで定義(記載)しない場合は「id」)
    protected $primaryKey = 'user_id';
    protected $keyType = 'string'; // 主キーの型を設定(文字列の場合は必須)(要確認)
    public $incrementing = false; // 主キーの自動採番設定(false:自動採番無効)デフォルトはtrue

    // 【推奨】fillable : 指定カラムのupdate(insert)を許可
    protected $fillable = [
        'name',
        'age',
    ];

    // guarded : 指定カラムの更新(挿入)を禁止 自動採番のPrimaryKey等を記述しておく
    protected $guarded = ['id'];

    // created_at, updated_at の設定 使用しない場合はfalse
    // protected $timestamps = false; // デフォルトはtrue

    // DB操作用のメソッド定義も見かけた
    public function getUser()
    {
        return $users;
    }
}

■Controller(DB操作の例) もちろんController以外で書くこと。

use App\Models\Sample;
use Illuminate\Support\Facades\DB;

public function index()
{
    // find : Modelオブジェクトを取得(PrimaryKey)
    $user = User::find(10);
    // Modelオブジェクトからカラムを取得
    $result = $user->name;

    // 基本型(分離)
    $query = User::query();
    $query->where('id', $id);
    $user = $query->get();

    // where[Column name] : レコードを取得 条件(カラム)指定
    $result = Sample::whereName('taro')->get();

    // where : レコードを取得 条件指定(自由)
    $result = Sample::where('name', 'Sato')->get();

    // count : 集計 レコード数を取得
    $result = Sample::where('color', 'red')->count();

    // create : レコードを追加
    // Modelに定義( protected $fillable = ['name']; )が必須
    // 戻り値 : 作成したObject
    $result = User::create([
        // カラム => 値
        'name' => 'Sato Taro',
        'password' => Hash::make($request->password),
    ]);

    // update : レコードを更新
    $result = Users::where('name', 'hanako')->update(['name' => 'sakura']);

    // update: 実践式
    // 自動で「updated_at」が更新される
    $user = User::find($user_id); // まずは主キーで一意にする
    $count = $user->update(['name' => 'sakura']);

    // delete: 実践式
    $query = User::query();
    $query->where('name', $name);
    $count = $query->delete();

    // SQLの結果をJSONで返却したい場合
    return $users->toJson();

    // 【実践】SELECTの結果が0件か確認したい
    // なぜこんなことをするかというと emptyが空配列をtrueとしてくれない変なバグで致命的な障害が起きたから
    $user = User::query()->get();
    if ($user->isEmpty()) { return null; };
}

■クエリビルダ(DB操作の例)

use Illuminate\Support\Facades\DB;

// 基本型
$users = DB::table('users')
    ->select('users.user_name')
    ->where('users.age', '>', '19')
    ->whereBetween('users.created_at', ['2024/01/01', '2024/12/31']) // BETWEEEN 第二引数:範囲
    ->whereIn('users.status', [1, 2]) // IN 第二引数:条件
    ->orderBy('users.user_id', 'desc')
    ->get();

// チェーンは分割してもよい ※公式ドキュメントより変数名「$query」
$query = DB::table('users');
$query->where('users.age', '>', '19');
$users = $query->get();

// value 値を1つのみ取得
$query = DB::table('users');
$query->where('users.id', $user_id);
$query->select('users.name');
$name = $query->value('name');

// JOIN: 実践
$result = DB::table('users')
    ->leftjoin('departments', 'users.department_id', '=', 'departments.id')
    ->where('users.id', $user_id)
    ->get();

// where 複数
// 配列でwhereを組む
$conditions = [
    ['users.id', $user_id]
];
$result = DB::table('users')
    ->where($conditions)
    ->get();

// Modelオブジェクトの操作
// 値の取得
$name = $users->name;

// ページネーション
$query = DB::table('users');
$query->where('users.age', '>', '18');
$users = $query->paginate(10); // 10レコードごとにページ切り替え
// ページネーションでページ切り替えするとテキストボックスの値が保持されない どうしたらいい
$users = $query->paginate(10)->withQueryString(); // クエリを引き継ぐ関数を追加

ローカルスコープ

複雑なSQLを簡易な記述で呼び出す。あらかじめModelに定義する。 Modelに定義しControllerから呼び出す。 関数名規則は「scope」+「用途名(頭文字は大文字)」。

// Model Class

// ローカルスコープ( 命名: scope + 用途名を大文字始まり) 
public function scopeActive($query)
{
    // Laravel9以降は「return」を省略推奨
    $query->where('is_active', '1');
}
// Controller

public function index($query)
{
    // ローカルスコープを呼び出す テーブル名::スコープ名
    $result = User::active()->get();

    // ローカルスコープのチェーンも便利
    $result = User:::active()->ageOver20()->get();
}

has One

1対1。主テーブルに紐づく従テーブルのレコードを取得。 「LEFT JOIN」のようなイメージ。

// Model 主テーブル

public function sub()
{
    // 引数: 従テーブルのモデル
    return $this->hasOne(Sub::class);
}
// Controller

public function index()
{
    // 主テーブル::条件->主テーブルモデルで定義したhasOne用メソッド
    $result = Main::find(1)->sub;
}

Seeder

テーブルにレコードを作成する。検証等で便利。

use Illuminate\Support\Facades\DB; //追加
use Illuminate\Support\Facades\Hash; //追加

class createUserSeeder extends Seeder
{
    public function run(): void
    {
        DB::table('users')->insert([
            'user_name' => Str::random(10),
            'email'     => Str::random(10).'@gmail.com',
            'password'  => Hash::make('password'),
            'password_expires_at' => '2020-01-01 00:00:00.000000',
        ]);
    }
}

トランザクション

手動とクロージャによるトランザクションがあるがクロージャが推奨されている。 例外が発生した場合自動的にロールバックされる。

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');
});

// トランザクション内部のスコープから外部の変数を扱うことはできない
// そのためuseで変数を渡す必要がある
DB::transaction(function () use ($name, $email, $password) {
    DB::update('update users set votes = 1');
});

エラーハンドリング

■ページネーションの矢印が大きすぎる https://qiita.com/EasyCoder/items/192590b5605b4b267c30