keigo-uの備忘録

沖縄で情報系の大学に通う大学生の技術ブログです。

明示的な結合

概要

Laravelのルートパラメータでは暗黙の結合により、パラメータ名と同名のモデルからidの対応する値を取得してくれますが、その結合を明示的に行うことで、id以外の値で取得したり、絞り込みなどを行う。

おさらい:暗黙の結合とは

// web.php
Route::get('posts/{post}', [PostController::class, 'show']);
// PostController.php
public function index(Post $post) {
    // 省略
}

上記のようなルートがあるときに、ルートパラメータである{post}の値をidとして、Postモデルのインスタンスを生成してコントローラーで受け取ることができる。

設定方法

明示的な結合を行うための設定方法はいくつかありますが、ここでは2つ紹介します。

RouteServiceProviderに記述する方法

app/Providers/RouteServiceProvider.phpのbootメソッドでパラメータ名に対応するモデルを指定します。

// 追記
use App\Models\Post;
use Illuminate\Support\Facades\Route;

/**
 * ルートモデルの結合、パターンフィルターなどを定義
 *
 * @return void
 */
public function boot()
{
    Route::bind('post', function ($value) {
        return Post::where('name', $value)->firstOrFail();
    });

    // 省略
}

Modelファイルに記述する方法

モデルファイルのresolveRouteBindingメソッドをオーバーライドすることでも設定することができます。

// Post.php
/**
 * 値と結合するモデルの取得
 * 
 */
public function resolveRouteBinding($value, $field = null)
{
    return $this->where('name', $value)->findOrFail($value);
}

JavaScriptで内容が変化するプルダウンメニューを実装する

概要

複数のプルダウンメニューがある状況で選択した結果によって次のプルダウンメニューの内容が変更する実装例を紹介します。
今回の例では、大学と学部の2つのプルダウンメニューがあり、大学を選択するとその大学の学部のみが選択肢になります。

環境

  • Laravel 9

前提条件

データベース

データベースにはすでに学校の情報が入っているとします。
今回はわかりやすいようにaに属する学部であればaaとなるようにしています。

universitiesテーブル

id name
1 a
2 b
3 c
4 d

facultiesテーブル

id university_id name
1 1 aa
2 1 ab
3 2 ba
4 2 bb
5 3 ca
6 3 cb
7 4 da
8 4 db

コントローラー

コントローラーからviewに対してデータベースの値を渡してください

public function pulldown()
{
    return view('pulldown')->with(['universities' => University::all(), 'faculties' => Faculties::all()]);
}

実装

<!-- 大学情報の入力欄 -->
<select id="university">
    <option value="">大学を選択してください</option>
    @foreach($universities as $university)
     <option value="{{ $university->id }}">{{ $university->name }}</option>
    @endforeach
</select>

<select id="faculty">
    <option value="">学部を選択してください</option>
    @foreach($faculties as $faculty)
    <option value="{{ $faculty->id }}">{{ $faculty->name }}</option>
    @endforeach
</select>

<script>
    const faculties = @json($faculties); // コレクションをjsonに変換
    const facultySelector = document.getElementById("faculty"); // 学部のセレクトタグ

    // universityのselectタグのonChangeイベントに関数を設定
    document.getElementById("university").onchange = e => {
        let selectedValue = e.target.value;
        facultySelector.options.length = 0; // 学部の選択肢を一旦削除
        
        // 「学部を選択してください」を選択時のリセット処理
        if(selectedValue == "") {
            var op = document.createElement("option");
            op.text = "学部を選択してください";
            facultySelector.appendChild(op);
                
            faculties.forEach(faculty => {
                var op = document.createElement("option");
                op.value = faculty.id;
                op.text = faculty.name;
                facultySelector.appendChild(op);
            });
            return;
        }
        
        var op = document.createElement("option");
        op.text = "学部を選択してください";
        facultySelector.appendChild(op);

        // 選択された大学のidでフィルタリングした後にメソッドチェーンでオプションタグの作成を行う
        faculties.filter(faculty => {
            return faculty.university_id == selectedValue;
        }).forEach(faculty => {
            var op = document.createElement("option");
            op.value = faculty.id
            op.text = faculty.name
            facultySelector.appendChild(op)
        });
    }
</script>

実行結果

未選択状態

未選択時

大学選択後

選択後

まとめ

Laravel環境でJavaScriptを用いて動的に変化するドロップダウンメニューを実装することができました。学部を選択後に学科をするような場合でもJSのコードを複製することで複数の動的なプルダウンメニューも実装できます。また、今回未選択時はすべての選択肢がでるようにしていますが、初期状態では選択肢が表示されないといった仕様でもいいのかなと思います。

Laravel × React × Inertiaで戻るボタンを実装する

概要

LaravelのSPAツールであるInertia環境下で前のページに戻るボタンを実装する。

環境

  • Laravel 9
  • Inertia
  • React

方法

Laravel側の実装

Inrertiaのshare dataを利用して、前のページのURLを渡す。

// middleware/HandleInertiaRequests.php
public function share(Request $request): array
{
    return array_merge(parent::share($request), [

        // その他のshare dataは省略

        'prevURL' => function() {
            return url()->previous();
        }
    ]);
}

React側の実装

usePageフックを使用し、share dataを受け取る

import { usePage, Link } from '@inertiajs/inertia-react'

const BackButton = () => {

    // usePageからprevURLを受け取る
    const prevURL = usePage().props.prevURL;

    return(
    <>
        <Link href={prevURL}>戻るボタン</Link>
    </>    
    );
}

export default BackButton;

リレーション先も含めてOR絞り込みをする

概要

Laravelアプリケーションにおいて、検索機能等を実装する際にリレーション先も含めてOR(または)で絞り込みを行う方法をまとめる。

環境

  • Laravel 9

方法

orWhereHas()を使用する。

$qury->where('title', '%{$keyword}%') // 通常の絞り込み
    ->orWhere('body', '%{$keyword}%') // 通常のOR絞り込み
    // 第2引数にリレーション先の絞り込み条件を指定するクロージャーを取る
    ->orWhereHas('categories', function($q) use ($keyword) {
        $q->where('name', '%{$keyword}%');
    })->get();

配列(コレクション)からpaginateを行う方法

概要

LaravelではEloquentでpaginateメソッドを用いることでページネーションをすることができるが、 自身で用意した配列(コレクション)を利用してページネーションを行う方法を調べたのをまとめようと思う。

環境

  • Laravel 9

方法

コレクションでページネーションをするためにはLengthAwarePaginatorを使用する。

use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator;

// 配列からコレクションへ変換
$collection = collect($array_data);

// 1ページごとの表示件数
$perPage = 6; 
// 現在のページを取得
$page = Paginator::resolveCurrentPage('page');
// ページ番号から表示するデータを指定
$pageData = $collection->slice(($page - 1) * $perPage, $perPage);
$options = [
        'path' => Paginator::resolveCurrentPath(),
        'pageName' => 'page'
];
$paginatedData = new LengthAwarePaginator($pageData, $collection->count(), $perPage, $page, $options); 

実行テスト

仮のコレクションデータを用意

$collection = collect(Post::all());

通常のpaginateで取得する場合と比較

dd(Post::paginate(6), $paginatedData);

実行結果
全く同じデータが取得できていることがわかる。
bladeで使用する際も{{ posts->links() }}でリンクを用意することができる。

クエリビルダでjoinをするとidが変わってしまう問題

概要と結論

リレーション先で検索を行うためleftJoinを用いてテーブルの結合を行ったが、 joinメソッドでは同名カラムが上書きされるため、selectでカラム名を変える必要がある

環境

  • Laravel 9

前提条件

テーブル構成

postsテーブル
int id PK
string title
string body
int category_id FK
categoriesテーブル
int id PK
string name

何をしようとしたのか

リレーション先のテーブルを検索するために結合を行った。

$query = Post::query();
$query->leftJoin('categories', 'posts.category_id', '=', 'categories.id');
$posts = $query->get();

joinとは?

joinメソッドは複数のテーブルを結合し、ひとつにまとめることができます。joinにはjoin(内部結合)、leftJoin(左外部結合)、rightJoin(右外部結合)の3つの種類があり、 Laravelではそれぞれクエリビルダで呼び出すことができます。

実行結果

結合はできているが、idがめちゃくちゃになっている。

実行結果

問題点

joinメソッドでは結合時にカラム名が同じ場合idが上書きされてしまう。 今回の場合、postsテーブルのidとcategoriesテーブルのidのカラム名が同じであったため上書きされていた。

解決策

selectメソッドを使用し、カラム名を変更する。

$query = Post::query();
// [追記]今回はcategoriesのidをcategory_idに変更
$query->select('posts.*', 'categories.id as category_id', 'categories.name');
$query->leftJoin('categories', 'posts.category_id', '=', 'categories.id');
$posts = $query->get();

実行結果

idが連番になっているので正しいことがわかる。

実行結果2

技術ブログはじめました。

はじめまして

皆さんこんにちは。沖縄の大学生keigo-uです。 私は個人でwebアプリやiOSアプリの開発を行っており、アルバイトでプログラミングスクールの講師をしています。 日々の開発の中で得た知識をより深めるために備忘録としてブログにまとめたいと思います。 スマブラと自炊、洋服が趣味なのですが趣味の投稿もするかもです。 当面は三日坊主にならないことを目標に頑張ります!

ポートフォリオ

実はちょっとしたポートフォリオサイトも作成したので良かったら見てください。

portfolio-nine-beta-93.vercel.app

扱う技術(予定も含め)

webアプリ

  • TypeScript
  • React
  • Next.JS

iOSアプリ

  • UIKit
  • SwiftUI
  • Combine
  • MVVM

アルバイト