はじめに
テーブルから取得したデータを、配列に再セットする例を記載します。
Laravelを使用した例ですが、再セットは手動での調整を行います。
コード例抜粋
・ビューに渡すデータを、テーブルより取得したデータをセット
1.テーブルよりデータ取得
2.ビュー用にデータセットし直し
3.ビューで使用
1.テーブルよりデータ取得
<?php
// ユーザー10がフォローしている人たちのデータを取得
$following = UserRelation::where('user_id', $userId)
->get(['target_user_id', 'is_following', 'is_blocking']);
2.ビュー用にデータセットし直し
テーブルから取得した値($following
)を基に、配列($relations
)に再セット
<?php
$relations = [];
// ユーザーがフォローしている人たちに関するデータを処理するループ
foreach ($following as $follow) {
// 初期化と同時にユーザーがフォローしている人たちの関係を設定
$relations[$follow->target_user_id] = [
'follow' => $follow->is_following, // ユーザーが他のユーザーをフォローしているかどうか
'follower' => false, // この段階ではフォロワー情報は不明なのでfalse
'mutual' => false, // 相互フォローの情報もこの段階では不明なのでfalse
'blocking' => $follow->is_blocking, // ユーザーが他のユーザーをブロックしているかどうか
'blocked_by' => false // この段階では誰にブロックされているかは不明なのでfalse
];
}
3.ビューで使用
以下のようにして使用
@foreach ($relations as $id => $relation)
<tr>
<td>{{ $id }}</td>
<td>{{ $relation['follow'] ? '○' : '' }}</td>
<td>{{ $relation['follower'] ? '○' : '' }}</td>
<td>{{ $relation['mutual'] ? '○' : '' }}</td>
<td>{{ $relation['blocking'] ? '○' : '' }}</td>
<td>{{ $relation['blocked_by'] ? '○' : '' }}</td>
</tr>
@endforeach
1,2で改善の余地があります。
今回は手動で行っていますが、Laravelでの配列の加工はCollectionを使います。
詳細
以下は配列の再セット例を試すまでの環境構築手順例や、テーブルの詳細な意味等の記載となります。
※以下sailコマンドの使用方法が違っています、読み飛ばしてください。
コード例
public function index()
{
$userId = 10; // 表示するユーザーIDを設定
// ユーザー10がフォローしている人たちのデータを取得
$following = UserRelation::where('user_id', $userId)
->get(['target_user_id', 'is_following', 'is_blocking']);
// ユーザー10をフォローしている人たちのデータを取得
$followers = UserRelation::where('target_user_id', $userId)
->get(['user_id', 'is_following', 'is_blocking']);
$relations = [];
// ユーザーがフォローしている人たちに関するデータを処理するループ
foreach ($following as $follow) {
// 初期化と同時にユーザーがフォローしている人たちの関係を設定
$relations[$follow->target_user_id] = [
'follow' => $follow->is_following, // ユーザーが他のユーザーをフォローしているかどうか
'follower' => false, // この段階ではフォロワー情報は不明なのでfalse
'mutual' => false, // 相互フォローの情報もこの段階では不明なのでfalse
'blocking' => $follow->is_blocking, // ユーザーが他のユーザーをブロックしているかどうか
'blocked_by' => false // この段階では誰にブロックされているかは不明なのでfalse
];
}
// ユーザーをフォローしている人たちに関するデータを処理するループ
foreach ($followers as $follower) {
// フォロワー情報が$relationsにまだ設定されていない場合、初期設定を行う
if (!isset($relations[$follower->user_id])) {
$relations[$follower->user_id] = [
'follow' => false, // フォロー情報はこの段階では不明なのでfalse
'follower' => false, // 初期設定でfalseを指定(この後で真偽値が設定される)
'mutual' => false, // 相互フォロー情報はこの段階では不明なのでfalse
'blocking' => false, // ブロッキング情報はこの段階では不明なのでfalse
'blocked_by' => false // ブロックされている情報はこの段階では不明なのでfalse
];
}
// フォロワーによるフォロワー情報設定
$relations[$follower->user_id]['follower'] = $follower->is_following && !$follower->is_blocking;
// $relations配列にフォロー情報があればそれを使い、なければfalseを設定
$relations[$follower->user_id]['follow'] = $relations[$follower->user_id]['follow'] ?? false;
// 互いにフォローし合っていれば相互フォローとしてtrueを設定
$relations[$follower->user_id]['mutual'] = $relations[$follower->user_id]['follow'] && $follower->is_following;
// フォロワーによるブロック情報を設定
$relations[$follower->user_id]['blocked_by'] = $follower->is_blocking && !$follower->is_following;
}
// 配列をキー(ID)で昇順にソート
ksort($relations);
return view('user_relations.index', compact('relations'));
}
<table>
<thead>
<tr>
<th>ID</th>
<th>Follow</th>
<th>Follower</th>
<th>Mutual</th>
<th>Blocking</th>
<th>Blocked By</th>
</tr>
</thead>
<tbody>
@foreach ($relations as $id => $relation)
<tr>
<td>{{ $id }}</td>
<td>{{ $relation['follow'] ? '○' : '' }}</td>
<td>{{ $relation['follower'] ? '○' : '' }}</td>
<td>{{ $relation['mutual'] ? '○' : '' }}</td>
<td>{{ $relation['blocking'] ? '○' : '' }}</td>
<td>{{ $relation['blocked_by'] ? '○' : '' }}</td>
</tr>
@endforeach
</tbody>
</table>
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserRelationController;
Route::resource('user-relations', UserRelationController::class);
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserRelation extends Model
{
use HasFactory;
}
データセット例
データ例
INSERT INTO user_relations (user_id, target_user_id, is_following, is_blocking) VALUES
(10, 11, 1, 0), -- ユーザー10はユーザー11をフォロー
(12, 10, 1, 0), -- ユーザー12はユーザー10をフォロー
(10, 13, 1, 0), -- ユーザー10はユーザー13をフォロー(相互フォローの一部)
(13, 10, 1, 0), -- ユーザー13はユーザー10をフォロー(相互フォローの一部)
(10, 14, 0, 1), -- ユーザー10はユーザー14をブロック
(15, 10, 0, 1), -- ユーザー15はユーザー10をブロック
(10, 16, 0, 1), -- ユーザー10はユーザー16をブロック(双方ブロックの一部)
(16, 10, 0, 1), -- ユーザー16はユーザー10をブロック(双方ブロックの一部)
(10, 17, 1, 0), -- ユーザー10はユーザー17をフォロー
(17, 10, 0, 1), -- ユーザー17はユーザー10をブロック
(10, 18, 0, 1), -- ユーザー10はユーザー18をブロック
(18, 10, 1, 0); -- ユーザー18はユーザー10をフォロー
テーブル内容
ユーザー関係テーブル (user_relations)
-
relation_id
(INT, PK): 関係の一意識別子 -
user_id
(INT, FK): アクションを起こすユーザーのID (users.user_id
に対する外部キー) -
target_user_id
(INT, FK): アクションの対象となるユーザーのID (users.user_id
に対する外部キー) -
is_following
(BOOLEAN): フォロー状態を示すフラグ -
is_blocking
(BOOLEAN): ブロック状態を示すフラグ -
created_at
(DATETIME): 関係が作成された日時 -
updated_at
(DATETIME): 関係が最後に更新された日時
CREATE TABLE IF NOT EXISTS user_relations (
relation_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
target_user_id INT NOT NULL,
is_following BOOLEAN NOT NULL,
is_blocking BOOLEAN NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (target_user_id) REFERENCES users(user_id)
);
本環境
Laravel Sailそ使用して環境を作りました。
Laravel Sail を使用している場合、php artisan
コマンドやマイグレーションなどの Laravel コマンドを実行するには、Laravel アプリケーションが動作している Docker コンテナにログインする必要があります。
以下の手順で Docker コンテナに入り、必要な操作を実行することができます:
前準備
コンテナを立ち上げます
./vendor/bin/sail up
-
コンテナにログイン:
Terminal で次のコマンドを実行して、Laravel アプリケーションが動作しているコンテナにログインします。docker exec -it example-app-laravel.test-1 /bin/bash
以下の場合、sail-8.3/app
イメージを使用しているコンテナが該当します。コンテナ名は example-app-laravel.test-1
です。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
09192b250321 sail-8.3/app "start-container" 18 hours ago Up 18 hours 0.0.0.0:80->80/tcp, 0.0.0.0:5173->5173/tcp, 8000/tcp example-app-laravel.test-1
cad6d45c7718 redis:alpine "docker-entrypoint.s…" 18 hours ago Up 18 hours (healthy) 0.0.0.0:6379->6379/tcp example-app-redis-1
04ca98ed2a51 selenium/standalone-chrome "/opt/bin/entry_poin…" 18 hours ago Up 18 hours 4444/tcp, 5900/tcp example-app-selenium-1
cba284f6d0ef mysql/mysql-server:8.0 "/entrypoint.sh mysq…" 18 hours ago Up 18 hours (healthy) 33060-33061/tcp, 0.0.0.0:3307->3306/tcp example-app-mysql-1
c368a370c238 axllent/mailpit:latest "/mailpit" 18 hours ago Up 18 hours (healthy) 0.0.0.0:1025->1025/tcp, 0.0.0.0:8025->8025/tcp, 1110/tcp example-app-mailpit-1
842122a5726a getmeili/meilisearch:latest "tini -- /bin/sh -c …" 18 hours ago Up 18 hours (unhealthy) 0.0.0.0:7700->7700/tcp example-app-meilisearch-1
2. Laravel コマンドの実行:
コンテナ内でシェルが開かれたら、必要な Laravel コマンド(例えば php artisan migrate
など)を実行できます。
3. コンテナからのログアウト:
作業が完了したら、exit
コマンドを使用してコンテナからログアウトします。
これで、Laravel Sail 環境内でデータベースのマイグレーションなどを行うことができます。
テーブル作成
Laravelで新しいテーブルを作成するためには、マイグレーションを使用するのが一般的です。以下の手順に従ってuser_relations
テーブルを作成します。
-
マイグレーションファイルの作成:
Laravelプロジェクトのルートディレクトリでターミナルを開き、以下のコマンドを実行して新しいマイグレーションファイルを生成します。php artisan make:migration create_user_relations_table
このコマンドは
database/migrations
ディレクトリに新しいマイグレーションファイルを作成します。ファイル名は日付と時間のプレフィックスが付いています。 -
マイグレーションファイルの編集:
生成されたマイグレーションファイルを開き、up
メソッドを編集してテーブル定義を追加します。以下のように編集します。public function up() { Schema::create('user_relations', function (Blueprint $table) { $table->id('relation_id'); $table->unsignedInteger('user_id'); $table->unsignedInteger('target_user_id'); $table->boolean('is_following'); $table->boolean('is_blocking'); $table->timestamps(); }); }
ここで、
$table->id()
は主キーであるrelation_id
を自動増分IDとして設定します。$table->unsignedInteger()
は外部キー用の符号なし整数型を定義しています。$table->boolean()
は真偽値のカラムを追加します。また、$table->timestamps()
はcreated_at
とupdated_at
のタイムスタンプを自動的に追加します。 -
マイグレーションの実行:
マイグレーションファイルが準備できたら、以下のコマンドを実行してデータベースにテーブルを作成します。php artisan migrate
このコマンドはマイグレーションをデータベースに適用し、
user_relations
テーブルが作成されます。
これでuser_relations
テーブルがLaravelアプリケーションのデータベースに正しく作成され、使用準備が整いました。次にこのテーブルを使用するためのモデルやコントローラーの実装に進めます。
テーブル作成確認
Laravel でテーブルが正しく作成されたかどうかを確認するには、いくつかの方法があります。以下に主な方法をいくつか紹介します:
1. Laravel Tinker を使用する
Laravel Tinker はコマンドラインから Laravel アプリケーションと対話するための強力なツールです。これを使ってデータベースにテーブルが存在するかどうかを確認できます。
-
コマンドラインで以下のコマンドを実行して Tinker を開始します:
php artisan tinker
-
Tinker が開いたら、次のコマンドを実行してテーブルの存在を確認します:
Schema::hasTable('user_relations');
このコマンドが
true
を返せば、テーブルは存在しています。
2. データベース管理ツールを使用する
データベースに直接接続して、テーブルが存在するかどうかを確認することができます。MySQL、PostgreSQLなどのデータベースであれば、phpMyAdmin、PgAdmin、または他のデータベース管理ツールを使用できます。
- データベースにログインします。
- データベースを選択し、存在するテーブルのリストを表示して
user_relations
テーブルを探します。
3. SQL コマンドを実行する
ターミナルまたはコマンドラインインターフェースから SQL クエリを直接実行してテーブルの存在を確認することもできます。
- MySQL の場合は次のようにコマンドを実行します:
mysql -u your_username -p -e "SHOW TABLES LIKE 'user_relations'" your_database_name
これらの方法を使用して user_relations
テーブルがデータベースに存在しているかどうかを確認できます。
テーブル内容
CREATE TABLE IF NOT EXISTS user_relations (
relation_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
target_user_id INT NOT NULL,
is_following BOOLEAN NOT NULL,
is_blocking BOOLEAN NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (target_user_id) REFERENCES users(user_id)
);
フォローテーブルとブロックテーブルを統合したアプローチです。
フォローとブロックの両方の状態を単一のテーブルで管理することができます。
ユーザー関係テーブル (user_relations)
-
relation_id
(INT, PK): 関係の一意識別子 -
user_id
(INT, FK): アクションを起こすユーザーのID (users.user_id
に対する外部キー) -
target_user_id
(INT, FK): アクションの対象となるユーザーのID (users.user_id
に対する外部キー) -
is_following
(BOOLEAN): フォロー状態を示すフラグ -
is_blocking
(BOOLEAN): ブロック状態を示すフラグ -
created_at
(DATETIME): 関係が作成された日時 -
updated_at
(DATETIME): 関係が最後に更新された日時
このテーブルでは、is_following
とis_blocking
のフラグを使って、ユーザー間の関係がフォローなのかブロックなのかを識別します。例えば、あるユーザーが他のユーザーをフォローしている場合、is_following
をTRUE
にし、is_blocking
はFALSE
に設定します。同様に、ユーザーが他のユーザーをブロックしている場合、is_blocking
をTRUE
にし、is_following
はFALSE
に設定します。相互に独立しているため、通常、is_following
とis_blocking
が同時にTRUE
になることはありませんが、ビジネス要件に応じて調整が必要です。
この設計により、データベースのテーブル数を減らし、ユーザー間の関係をよりシンプルに管理できる可能性があります。ただし、フォローとブロックが相互排他的な操作として扱われることが多いため、ビジネスロジックを通じてこれらの状態を適切に管理し、矛盾しないようにする必要があります。
また、性能面やクエリの複雑性、将来的な拡張性を考慮して、この統合アプローチが適切かどうかを慎重に評価することが重要です。特に大規模なデータセットや多くのユーザー間の関係を扱う場合、インデックスの設計やクエリの最適化に特に注意を払う必要があります。
データ追加
is_following
カラムがデータ挿入の SQL に抜けているため、まずはこれを適切に追記する必要があります。各行に is_following
の値を適切に追加していきましょう。フォローの関係がある場合は 1
(true) 、ない場合は 0
(false) を設定します。
以下の SQL コマンドで user_relations
テーブルにデータを追加します。各行に is_following
の値を追加しました。
INSERT INTO user_relations (user_id, target_user_id, is_following, is_blocking) VALUES
(10, 11, 1, 0), -- ユーザー10はユーザー11をフォロー
(12, 10, 1, 0), -- ユーザー12はユーザー10をフォロー
(10, 13, 1, 0), -- ユーザー10はユーザー13をフォロー(相互フォローの一部)
(13, 10, 1, 0), -- ユーザー13はユーザー10をフォロー(相互フォローの一部)
(10, 14, 0, 1), -- ユーザー10はユーザー14をブロック
(15, 10, 0, 1), -- ユーザー15はユーザー10をブロック
(10, 16, 0, 1), -- ユーザー10はユーザー16をブロック(双方ブロックの一部)
(16, 10, 0, 1), -- ユーザー16はユーザー10をブロック(双方ブロックの一部)
(10, 17, 1, 0), -- ユーザー10はユーザー17をフォロー
(17, 10, 0, 1), -- ユーザー17はユーザー10をブロック
(10, 18, 0, 1), -- ユーザー10はユーザー18をブロック
(18, 10, 1, 0); -- ユーザー18はユーザー10をフォロー
この SQL スクリプトをデータベースに対して実行することで、データがテーブルに挿入されます。Laravel Sail を使用している場合、コンテナ内でこの SQL を実行する必要があります。コンテナ内で SQL コマンドを実行するには、以下のようにします:
-
Laravel アプリケーションが動作している Docker コンテナにログインします。
docker exec -it example-app-laravel.test-1 /bin/bash
-
MySQL コマンドラインにアクセスします(MySQL コンテナに接続するための情報が必要です)。.envの設定によります。
mysql -h mysql -P 3306 -u sail -p
3. 上記の SQL コマンドを実行します。
これでデータが適切に挿入されます。何か問題が発生した場合、またはさらなる支援が必要な場合はお知らせください。
ルーティング等準備
Laravelでモデルとコントローラを作成するための artisan
コマンドを使った準備を行います。
モデルの作成
UserRelation
モデルを作成するには、以下のコマンドを実行します。このコマンドは、モデルのファイルを app/Models
ディレクトリに生成します。
php artisan make:model UserRelation
コントローラの作成
UserRelationController
コントローラを作成するには、以下のコマンドを実行します。このコマンドは、コントローラのファイルを app/Http/Controllers
ディレクトリに生成し、基本的な RESTful アクションを備えたコントローラを作成します。
php artisan make:controller UserRelationController --resource
このコマンドにより、index
, create
, store
, show
, edit
, update
, destroy
メソッドが持つリソースコントローラが生成されます。ただし、今回のケースでは主に index
メソッドだけを使います。
ルーティングの設定
コントローラが生成されたら、ルートを設定する必要があります。routes/web.php
ファイルを開き、以下のルートを追加してアクセスできるようにします。
Route::resource('user-relations', UserRelationController::class);
このルート定義により、user-relations
への URL パスが UserRelationController
の各アクションにマッピングされます。
これで、モデルとコントローラの準備が整いました。次に、コントローラの index
メソッドを編集して、ビジネスロジックを実装していく必要があります。これには、データベースから関連情報を取得してビューに渡す処理を記述します。
コード例
コントローラ
以下に、解説したコードの各部分を詳細に説明します。これはユーザー間の関係(フォロー、フォロワー、相互フォロー、ブロック、ブロックされる)を把握し、ID順にソートして表示するための Laravel のコントローラ関数です。
コード全体の流れ
-
データ取得:
ユーザー10の関係を示すデータをデータベースから取得します。following
はユーザー10がフォローしている人たち、followers
はユーザー10をフォローしている人たちです。 -
配列の初期化と設定:
ユーザー間の関係を格納するための$relations
配列を初期化し、各関係を設定します。 -
フォロー情報の処理:
ユーザー10がフォローしている人たちに関する情報を$relations
配列に追加します。 -
フォロワー情報の処理:
ユーザー10をフォローしている人たちに関する情報を処理し、必要に応じて$relations
配列を更新します。 -
配列のソート:
ksort()
関数を使用して、$relations
配列をキー(ユーザーID)で昇順にソートします。 -
ビューへのデータ渡し:
ソートされた$relations
配列をビューに渡し、ビューでこれを表示します。
コードの詳細解説
public function index()
{
$userId = 10; // 表示するユーザーIDを設定
// ユーザー10がフォローしている人たちのデータを取得
$following = UserRelation::where('user_id', $userId)
->get(['target_user_id', 'is_following', 'is_blocking']);
// ユーザー10をフォローしている人たちのデータを取得
$followers = UserRelation::where('target_user_id', $userId)
->get(['user_id', 'is_following', 'is_blocking']);
// ユーザ間の関係を格納
$relations = [];
// フォロー情報
// 表示するユーザがフォロー、もしくはブロックしている状況を記載
foreach ($following as $follow) {
$relations[$follow->target_user_id] = [
'follow' => $follow->is_following,
'follower' => false, // 初期値はfalse
'mutual' => false, // 初期値はfalse
'blocking' => $follow->is_blocking,
'blocked_by' => false // 初期値はfalse
];
}
// フォロワー情報
// 表示するユーザがフォローされている、ブロックされている、また相互フォロー状態かを記載
foreach ($followers as $follower) {
if (!isset($relations[$follower->user_id])) {
$relations[$follower->user_id] = [
'follow' => false, // 初期値はfalse
'follower' => false, // 初期値はfalse
'mutual' => false, // 初期値はfalse
'blocking' => false, // 初期値はfalse
'blocked_by' => false // 初期値はfalse
];
}
$relations[$follower->user_id]['follower'] = true && !$follower->is_blocking;
$relations[$follower->user_id]['follow'] = $relations[$follower->user_id]['follow'] ?? false;
$relations[$follower->user_id]['mutual'] = $relations[$follower->user_id]['follow'] && $follower->is_following;
$relations[$follower->user_id]['blocked_by'] = $follower->is_blocking;
}
// 配列をキー(ID)で昇順にソート
ksort($relations);
return view('user_relations.index', compact('relations'));
}
重要ポイント
- 配列の初期化とデフォルト値: 各ユーザー
IDに対して、フォロー、フォロワーなどの状態を示すキーと値のペアを設定します。これにより、ビューで表示する際に未定義のキーによるエラーを避けます。
-
条件付き更新:
$follower->is_blocking
が真の場合、そのユーザーはフォロワーとしてカウントされません。 -
データのソート:
ksort()
を使用して、IDに基づいて配列をソートします。これにより、ビューでの表示順がIDの昇順になります。
このような処理フローにより、ユーザー間の複雑な関係を効果的に管理し、視覚的にわかりやすく表示することができます。
- 要件が複雑になる場合の対応
このロジックの主な目的は、ユーザー間の関係が複雑になるケース(例えば、相互にフォローしあっているが、片方がもう片方をブロックしている状況)を正確に表示することです。ユーザーが他のユーザーをブロックしている場合、通常はそのユーザーのアクション(例えば投稿の表示など)を制限したいと考えるため、そのようなユーザーを「フォロワー」として表示しない対応が必要になることがあります。
参考記事