0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravelでページネーションを試したメモ

Posted at

はじめに

https://qiita.com/kenji123/items/a78e85ffc9309fad10bb
Laravelのコレクションに対し、ページネーションの使用を行っています。
今回のケースでは、データベースから直接ページネーションするのは断念し、Laravelのコレクションを使用してデータを処理し、その後でページネーションを適用しました。

SQL vs コレクションベースのページネーション

通常、データを効率的にページネーションするためには、データベースクエリ自体でpaginate()メソッドを使用するのが最も効率的です。これにより、必要なデータのみがデータベースから取得され、アプリケーションへの負荷が軽減されます。

しかし、データの加工が必要でクエリだけではページネーションが困難な場合は、データベースから全データを取得してからコレクションを使って加工し、forPage() メソッドや slice() メソッドなどを用いて手動でページネーションを行うことができます。

コレクションベースでの対応

ID取得後、ページネーション適用に変更しました。
Laravelのコレクションを活用しています。

  1. フォローしているユーザーのIDと、その関係を取得

    • ユーザーID=10がフォローしているユーザーのIDリストを取得します。
  2. フォロワーのIDとその関係を取得

    • ユーザーID=10をフォローしているユーザーのIDリストを取得します。
  3. IDリストをユニークにする

    • フォローとフォロワーのIDリストを統合し、重複を排除してユニークなリストを作成します。
  4. 関係の詳細を確認し、データを組み立てる

    • ユニークなIDリストに基づいて、各ユーザーとユーザーID=10との間の詳細な関係(相互フォロー、ブロック状態など)を設定します。
  5. ページネーションの適用

    • 最終的なコレクションに対して LengthAwarePaginator を使用してページネーションを行い、ビューにデータを渡します。

上記コードの疑似コード例

以下は上記プロセスを実装する擬似コードです:

$userId = 10;
$followingIds = UserRelation::where('user_id', $userId)->get()->pluck('target_user_id');
$followersIds = UserRelation::where('target_user_id', $userId)->get()->pluck('user_id');

// IDリストをユニークにする
$uniqueIds = $followingIds->merge($followersIds)->unique();

$relationships = collect();
foreach ($uniqueIds as $id) {
    // このIDの詳細な関係を構築
    $isFollowing = $followingIds->contains($id);
    $isFollower = $followersIds->contains($id);
    $relationships->push([
        'id' => $id,
        'follow' => $isFollowing,
        'follower' => $isFollower,
        'mutual' => $isFollowing && $isFollower,
        // 他の関係のデータもここで加える
    ]);
}

// LengthAwarePaginatorを使用してページネーション
$perPage = 10;
$page = LengthAwarePaginator::resolveCurrentPage();
$paginatedItems = new LengthAwarePaginator(
    $relationships->forPage($page, $perPage),
    $relationships->count(),
    $perPage,
    $page,
    ['path' => request()->url(), 'query' => request()->query()]
);

return view('user_relations.index', ['relations' => $paginatedItems]);

このコードは、特定のビジネスロジックに基づいてユーザー関係を処理し、それをページネーションしてビューに表示する基本的な流れを示しています。この方法は、データベースからの直接的なページネーションが困難な場合や、特定の処理を行う必要がある場合に特に有用です。

今回のコード例

前回にページネーションを適用下もコードです。

app/Http/Controllers/UserRelations.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\UserRelation;
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;

class UserRelationController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $userId = 10; // 表示するユーザーIDを設定
    
        // フォローしているユーザーのデータを取得
        $following = UserRelation::where('user_id', $userId)
                            ->get(['target_user_id', 'is_following', 'is_blocking'])
                            ->keyBy('target_user_id');
    
        // フォロワーのデータを取得
        $followers = UserRelation::where('target_user_id', $userId)
                            ->get(['user_id', 'is_following', 'is_blocking'])
                            ->keyBy('user_id');
                            var_dump($followers);var_dump('aaaaaaaaaaaa');
        // 全ユーザーIDの一覧を取得
        $allUserIds = $following->keys()->merge($followers->keys())->unique();
        
        // 全データを統合
        $allRelations = $allUserIds->mapWithKeys(function ($id) use ($following, $followers) {
            $isFollowing = $following->has($id) && $following[$id]->is_following;
            $isFollower = $followers->has($id) && $followers[$id]->is_following;
            $isMutual = $isFollowing && $isFollower;
            $isBlocking = $following->has($id) ? $following[$id]->is_blocking : false;
            $isBlockedBy = $followers->has($id) ? $followers[$id]->is_blocking : false;
    
            return [$id => [
                'follow' => $isFollowing ? '○' : '',
                'follower' => $isFollower ? '○' : '',
                'mutual' => $isMutual ? '○' : '',
                'blocking' => $isBlocking ? '○' : '',
                'blocked_by' => $isBlockedBy ? '○' : ''
            ]];
        })->sortKeys();  // IDでソート
    
        // ページネーションの設定
        $perPage = 3;
        $currentPage = LengthAwarePaginator::resolveCurrentPage();
        $currentItems = $allRelations->slice(($currentPage - 1) * $perPage, $perPage)->all();
    
        // LengthAwarePaginatorを使ってページネーションを適用
        $paginatedItems = new LengthAwarePaginator(
            $currentItems, 
            $allRelations->count(), 
            $perPage, 
            $currentPage, [
            'path' => LengthAwarePaginator::resolveCurrentPath(),
            ]
        );
    
        return view('user_relations.index', ['relations' => $paginatedItems]);
    }
    
}

表示例
image.png

resources/views/user_relations/index.blade.html
<link rel="stylesheet" href="/css/style.css">

<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>

{{ $relations->links() }}
public/css/style.css

svg {
  width: 20px; /* SVGのサイズ調整 */
  height: 20px;
}


.relative.z-0.inline-flex.rtl\:flex-row-reverse.shadow-sm.rounded-md {
  justify-content: center; /* 中央揃えにする場合 */
  padding: 10px; /* パディングを追加 */
}

.relative.inline-flex.items-center.px-4.py-2.-ml-px.text-sm.font-medium {
  margin: 0 5px; /* 左右のマージンを調整 */
  border-radius: 4px; /* 角の丸みを調整 */
}
.hover\:text-gray-400, .focus\:ring-gray-300, .active\:bg-gray-100 {
  transition: all 0.3s ease; /* スムーズな遷移効果 */
}

上記styleで調整しているのは、< > の表示がおかしく一旦応急処置で対応。
image.png

コレクションベースのページネーションの欠点

  • メモリ使用量: 全データをアプリケーションメモリ上にロードするため、大量のデータを扱う場合はメモリオーバーヘッドが大きくなります。
  • パフォーマンス: 特に大規模なデータセットの場合、データベースから全てのデータを取得し、アプリケーションレベルで処理を行うため、レスポンスタイムが長くなる可能性があります。

特に大きなデータセットに対して、メモリ使用量とパフォーマンスに関する懸念が生じます。以下の理由で問題が生じる可能性があります:
今回の場合の懸念です。

  1. 大量のデータの取得と保持:
    get()メソッドは、対象のレコードをデータベースから全てメモリにロードします。特にuser_idtarget_user_idが多数の関連を持つ場合、これは非常にメモリ集約的になり得ます。

  2. 全ユーザーIDの計算:
    keys()->merge()unique()の使用は、大きなコレクションを生成し、これもまた大量のメモリを消費します。

  3. 全データの再加工:
    mapWithKeysを用いたデータの再加工は、すでにメモリにロードされたデータに対してさらに処理を行い、大規模なデータセットでは効率が悪くなります。

  4. ページネーションの手動実装:
    ここではLengthAwarePaginatorを使っていますが、これは元のデータセットを既に全てメモリ上にロードした後で適用されるため、理想的なデータベースクエリによるページネーションと比較して非効率的です。

複数のデータソースの統合

user_idに基づいてフォローしているユーザーを取得し、target_user_idに基づいてフォロワーを取得する必要があるため、これら二つのデータセットをうまく統合することが課題となっています。

  1. Eloquentリレーションの最適化:
    LaravelのEloquentリレーションを使用して、関連するデータを事前にwith()またはload()することで、各ユーザーに対してフォローとフォロワーの情報を効率的に取得します。これにより、複数のクエリを一つにまとめてパフォーマンスを向上させることが可能です。

  2. 中間テーブルの利用:
    フォロー関係を管理する中間テーブルを作成し、フォローとフォロワーの情報を一つのテーブルに統合することで、クエリの複雑さを減らすことができます。これにより、ページネーション時に必要なデータのみを効率的に取得することが可能になります。

  3. 複合クエリの実行:
    SQLのJOINクエリを使用して、user_idtarget_user_idに基づく情報を一度に取得し、それに基づいて必要な情報を抽出します。これはデータベースレベルでの結合処理になるため、アプリケーションサーバーのメモリ負荷を減らすことができます。

具体的なコード例

例として、Eloquentリレーションを最適化して複数のデータソースを統合する方法を示します。以下の例では、Userモデルにfollowersfollowingsというリレーションを定義し、これを利用してデータを取得します。

// User モデル
public function followers()
{
    return $this->belongsToMany(User::class, 'user_relations', 'target_user_id', 'user_id')
                ->withPivot('is_following', 'is_blocking');
}

public function followings()
{
    return $this->belongsToMany(User::class, 'user_relations', 'user_id', 'target_user_id')
                ->withPivot('is_following', 'is_blocking');
}

// コントローラー
public function index()
{
    $userId = 10;
    $perPage = 10;

    $user = User::with(['followers', 'followings'])->find($userId);

    // フォロワーとフォロー情報を取得
    $followers = $user->followers;
    $followings = $user->followings;

    // 必要に応じて追加の処理をここに記述

    return view('user_relations.index', [
        'followers' => $followers,
        'followings' => $followings
    ]);
}

このコードは、特定のユーザーのフォロワーとフォロー情報を効率的にロードし、それらをビューに渡す基本的な方法を示しています。さらに、これらのリレーションをページネーションすることも可能ですが、リレーションのページネーションは少し複雑になるため、必要に応じてさらにカスタマイズが必要になります。

おわりに

今回はLaravelコレクションの使用理解を主にしています。
Laravelのコレクションによるページネーションは避けた方がいいかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?