فرض کنید در یک فروشگاه اینترنتی چند هزار محصول دارید و میخواهید همه آنها را در صفحه محصولات نمایش دهید.
اگر تمام محصولات را یکجا از دیتابیس دریافت کنید، حجم زیادی از اطلاعات وارد برنامه میشود و نمایش آنها نیز برای کاربر مناسب نخواهد بود.
راهحل این مشکل Pagination یا صفحهبندی است.
Pagination به ما اجازه میدهد تعداد زیادی رکورد را به بخشهای کوچکتر تقسیم کنیم و هر بار فقط تعداد مشخصی از آنها را نمایش دهیم.
Laravel برای این کار سیستم Pagination قدرتمندی در اختیار توسعهدهندگان قرار داده است که با Eloquent، Query Builder و حتی APIها قابل استفاده است.
در این مقاله یاد میگیریم Pagination در Laravel چیست، چگونه از paginate() استفاده کنیم، تفاوت paginate() با simplePaginate() و cursorPaginate() چیست، چگونه لینک صفحات را نمایش دهیم و چگونه Pagination را برای پروژههای واقعی پیادهسازی کنیم.
# Pagination در Laravel چیست؟
Pagination به معنی صفحهبندی دادهها است.
فرض کنید در جدول products تعداد 10,000 محصول داریم.
به جای اینکه هر 10,000 محصول را یکجا دریافت کنیم، میتوانیم مثلاً در هر صفحه 20 محصول نمایش دهیم:
صفحه 1 → محصولات 1 تا 20
صفحه 2 → محصولات 21 تا 40
صفحه 3 → محصولات 41 تا 60
...
در این حالت کاربر میتواند بین صفحات حرکت کند.
Pagination علاوه بر بهبود تجربه کاربری، میتواند باعث کاهش حجم داده دریافتشده و کاهش فشار روی برنامه و دیتابیس شود.
# چرا Pagination اهمیت دارد؟
فرض کنید جدول users یک میلیون رکورد دارد.
اگر بنویسیم:
$users = User::all();
Laravel باید تمام رکوردها را دریافت کند.
این کار در چنین شرایطی میتواند مصرف حافظه و زمان اجرای Query را افزایش دهد.
اما با Pagination:
$users = User::paginate(20);
در هر صفحه فقط تعداد مشخصی از رکوردها دریافت میشوند.
به همین دلیل Pagination یکی از ابزارهای مهم در پروژههایی است که با دادههای زیاد سروکار دارند.
# سادهترین روش Pagination در Laravel
اگر از Eloquent استفاده میکنیم، کافی است به جای get() از paginate() استفاده کنیم.
مثلاً:
$users = User::paginate(20);
عدد 20 یعنی در هر صفحه 20 کاربر نمایش داده شود.
Laravel خودش اطلاعات موردنیاز برای صفحهبندی را آماده میکند.
# Pagination با Query Builder
Pagination فقط برای Eloquent نیست.
با Query Builder نیز میتوانیم از Pagination استفاده کنیم:
use Illuminate\Support\Facades\DB;
$users = DB::table('users')->paginate(20);
در اینجا نیز در هر صفحه 20 رکورد نمایش داده میشود.
این قابلیت مخصوصاً زمانی کاربردی است که به جای Model مستقیماً با Query Builder کار میکنیم.
# paginate() چگونه کار میکند؟
فرض کنید:
$products = Product::paginate(20);
Laravel متوجه میشود که:
- تعداد آیتمهای هر صفحه 20 است.
- صفحه فعلی باید از URL مشخص شود.
- رکوردهای مربوط به همان صفحه باید دریافت شوند.
- اطلاعات مربوط به تعداد صفحات باید محاسبه شود.
برای مثال URL میتواند به شکل زیر باشد:
/products?page=2
در این حالت Laravel متوجه میشود که کاربر در صفحه دوم قرار دارد.
# ساخت Pagination در Controller
یک Controller ساده برای محصولات میتواند به شکل زیر باشد:
namespace App\Http\Controllers;
use App\Models\Product;
class ProductController extends Controller
{
public function index()
{
$products = Product::paginate(20);
return view('products.index', compact('products'));
}
}
در اینجا متغیر $products شامل دادههای صفحه فعلی و اطلاعات Pagination است.
# نمایش Pagination در Blade
یکی از مزیتهای Laravel این است که برای نمایش لینکهای Pagination نیز امکانات آماده دارد.
در فایل Blade:
{{ $products->links() }}
Laravel لینکهای مربوط به صفحات را تولید میکند.
بنابراین یک View ساده میتواند به شکل زیر باشد:
@foreach ($products as $product)
<h2>{{ $product->name }}</h2>
<p>{{ $product->price }}</p>
@endforeach
{{ $products->links() }}
در اینجا ابتدا محصولات صفحه فعلی نمایش داده میشوند و سپس لینکهای Pagination قرار میگیرند.
# ساخت یک صفحه محصولات کامل
فرض کنید Route زیر را داریم:
use App\Http\Controllers\ProductController;
Route::get('/products', [ProductController::class, 'index']);
Controller:
public function index()
{
$products = Product::paginate(12);
return view('products.index', compact('products'));
}
و View:
<h1>محصولات</h1>
@foreach ($products as $product)
<article>
<h2>{{ $product->name }}</h2>
<p>{{ number_format($product->price) }} تومان</p>
</article>
@endforeach
{{ $products->links() }}
حالا اگر URL صفحه اول:
/products
باشد، برای صفحه دوم:
/products?page=2
استفاده میشود.
# تعداد آیتمهای هر صفحه
مقداری که به paginate() میدهیم، تعداد آیتمهای هر صفحه را مشخص میکند.
مثلاً:
Product::paginate(10);
یعنی 10 محصول در هر صفحه.
یا:
Product::paginate(50);
یعنی 50 محصول در هر صفحه.
برای مثال:
| کد | تعداد آیتم در صفحه |
|---|---|
paginate(10) | 10 |
paginate(20) | 20 |
paginate(50) | 50 |
paginate(100) | 100 |
تعداد مناسب باید بر اساس نوع محتوا و تجربه کاربری انتخاب شود.
# تغییر نام پارامتر صفحه
بهصورت پیشفرض Laravel از پارامتر page در URL استفاده میکند.
مثلاً:
/products?page=3
در بعضی شرایط ممکن است بخواهید نام پارامتر متفاوت باشد.
برای این کار میتوان پارامتر صفحه را مشخص کرد:
Product::paginate(
20,
['*'],
'products_page'
);
حالا پارامتر صفحه میتواند به شکل زیر باشد:
/products?products_page=2
این قابلیت زمانی مفید است که در یک صفحه چند Pagination مختلف داشته باشید.
# Pagination همراه با where
Pagination را میتوان با شرطها ترکیب کرد.
مثلاً فقط محصولات فعال:
$products = Product::where('status', 'active')
->paginate(20);
یا محصولات یک دستهبندی:
$products = Product::where('category_id', $categoryId)
->paginate(20);
به این ترتیب Pagination فقط روی نتایج فیلترشده اعمال میشود.
# Pagination همراه با orderBy
میتوان قبل از Pagination نتایج را مرتب کرد:
$products = Product::orderBy('created_at', 'desc')
->paginate(20);
یا:
$products = Product::orderBy('price', 'asc')
->paginate(20);
مثلاً برای فروشگاه میتوان جدیدترین محصولات را نمایش داد:
$products = Product::latest()
->paginate(24);
# Pagination همراه با Search
یکی از کاربردهای بسیار مهم Pagination، ترکیب آن با جستجو است.
فرض کنید کاربر محصولی را جستجو میکند:
$products = Product::where('title', 'like', "%{$keyword}%")
->paginate(20);
اما یک نکته مهم وجود دارد.
وقتی کاربر وارد صفحه دوم میشود، باید مقدار Search نیز در URL حفظ شود.
برای این کار میتوان از:
->withQueryString()
استفاده کرد.
مثلاً:
$products = Product::where('title', 'like', "%{$keyword}%")
->paginate(20)
->withQueryString();
اگر URL اولیه:
/products?search=laravel
باشد، هنگام رفتن به صفحه دوم پارامتر Search نیز حفظ میشود.
# حفظ Query String در Pagination
فرض کنید فیلترهای زیر را داریم:
/products?category=5&status=active
اگر Pagination انجام دهیم، بهتر است لینک صفحه دوم نیز فیلترها را حفظ کند.
میتوانیم بنویسیم:
$products = Product::query()
->where('status', 'active')
->paginate(20)
->withQueryString();
در نتیجه پارامترهای موجود در URL در لینکهای Pagination نیز حفظ میشوند.
این قابلیت برای صفحات جستجو و فیلتر محصولات بسیار مهم است.
# حفظ پارامترهای مشخص با appends()
گاهی نمیخواهیم تمام Query String حفظ شود.
در چنین شرایطی میتوان پارامترهای موردنظر را اضافه کرد:
$products = Product::paginate(20)
->appends([
'category' => 5,
]);
در این حالت لینکهای Pagination شامل پارامتر category خواهند بود.
# تفاوت paginate() و simplePaginate()
Laravel چند روش مختلف برای Pagination دارد.
دو مورد مهم:
paginate()
و:
simplePaginate()
هستند.
paginate() اطلاعات کاملتری درباره Pagination در اختیار Laravel قرار میدهد و میتواند تعداد صفحات و لینکهای مربوط به آنها را ایجاد کند.
اما simplePaginate() بیشتر روی حرکت بین صفحه قبلی و بعدی تمرکز دارد.
مثلاً:
$users = User::simplePaginate(20);
در رابط کاربری معمولاً چیزی شبیه این مفهوم خواهیم داشت:
← قبلی بعدی →
در حالی که paginate() میتواند لینک صفحات را نیز در اختیار View قرار دهد:
← قبلی 1 2 3 4 5 بعدی →
# چه زمانی simplePaginate() بهتر است؟
اگر تعداد بسیار زیادی رکورد دارید و نیازی به نمایش تعداد کل صفحات ندارید، simplePaginate() میتواند گزینه مناسبی باشد.
مثلاً:
$logs = LogEntry::latest()
->simplePaginate(50);
برای صفحات Log، Activity و لیستهای طولانی که کاربر صرفاً بین نتایج حرکت میکند، این روش میتواند مناسب باشد.
# cursorPaginate() چیست؟
Laravel روش دیگری به نام cursorPaginate() نیز در اختیار قرار میدهد.
مثلاً:
$users = User::orderBy('id')
->cursorPaginate(20);
در این روش Pagination به جای تکیه بر شماره صفحه، از Cursor برای مشخص کردن موقعیت رکوردها استفاده میکند.
این روش برای دادههای بسیار زیاد و سناریوهایی که حرکت ترتیبی بین دادهها اهمیت دارد، میتواند گزینه مناسبی باشد.
# تفاوت paginate، simplePaginate و cursorPaginate
بهصورت خلاصه:
| روش | کاربرد |
|---|---|
paginate() | Pagination کامل با صفحات و اطلاعات بیشتر |
simplePaginate() | Pagination ساده قبلی/بعدی |
cursorPaginate() | حرکت مبتنی بر Cursor در دادهها |
برای یک پروژه معمولی، paginate() اغلب اولین گزینهای است که باید بررسی شود.
اگر تعداد دادهها بسیار زیاد باشد یا نوع Query و نحوه پیمایش دادهها چنین چیزی را ایجاب کند، میتوان سراغ روشهای دیگر رفت.
# Pagination با Query Builder
مثلاً:
$products = DB::table('products')
->where('status', 'active')
->orderBy('created_at', 'desc')
->paginate(20);
و در Blade:
@foreach ($products as $product)
<h2>{{ $product->title }}</h2>
@endforeach
{{ $products->links() }}
بنابراین Pagination به Eloquent وابسته نیست.
# Pagination و انتخاب ستونها
همانند Queryهای معمول، بهتر است اگر تمام ستونهای جدول را نیاز ندارید، فقط ستونهای موردنیاز را دریافت کنید.
مثلاً:
$products = Product::select([
'id',
'title',
'price',
'thumbnail',
])
->paginate(20);
این کار بهخصوص در جدولهای بزرگ یا Queryهای پیچیده میتواند مفید باشد.
# Pagination و Relationship
اگر از Relationshipهای Eloquent استفاده میکنیم نیز میتوان Pagination داشت.
فرض کنید هر کاربر سفارشهایی دارد:
$user->orders()->paginate(20);
در این حالت سفارشهای کاربر بهصورت صفحهبندیشده دریافت میشوند.
این قابلیت در پنلهای مدیریتی بسیار کاربردی است.
# Pagination در پنل مدیریت
فرض کنید در پنل مدیریت میخواهیم کاربران را نمایش دهیم:
$users = User::latest()->paginate(30);
در View:
<table>
@foreach ($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
</tr>
@endforeach
</table>
{{ $users->links() }}
به این شکل لازم نیست تمام کاربران را یکجا در پنل نمایش دهیم.
# Pagination در API
Pagination در APIها نیز بسیار مهم است.
فرض کنید یک API برای دریافت محصولات داریم:
public function index()
{
$products = Product::paginate(20);
return response()->json($products);
}
Laravel اطلاعات Pagination را در پاسخ API قرار میدهد.
این اطلاعات میتواند برای اپلیکیشن موبایل یا Frontend مورد استفاده قرار گیرد.
# دریافت تعداد آیتمها از Request
در بعضی APIها یا پنلها ممکن است بخواهیم کاربر تعداد آیتمهای هر صفحه را مشخص کند.
مثلاً:
$perPage = request()->integer('per_page', 20);
$products = Product::paginate($perPage);
در این حالت اگر کاربر مقدار زیر را ارسال کند:
?per_page=50
تعداد نتایج صفحه میتواند 50 باشد.
اما باید مراقب باشید که کاربر نتواند مقدار غیرمنطقی مانند چند هزار رکورد را درخواست کند.
بهتر است یک سقف تعیین کنید:
$perPage = min(
request()->integer('per_page', 20),
100
);
$products = Product::paginate($perPage);
در این مثال حداکثر 100 رکورد در هر صفحه دریافت میشود.
# طراحی Pagination در Laravel
Laravel میتواند HTML موردنیاز Pagination را تولید کند، اما ظاهر نهایی آن به Frontend و تنظیمات پروژه نیز بستگی دارد.
در یک پروژه میتوانید Pagination را با طراحی سایت هماهنگ کنید.
مثلاً:
<div class="pagination">
{{ $products->links() }}
</div>
سپس با CSS یا سیستم UI پروژه ظاهر آن را تنظیم کنید.
اگر از Tailwind CSS استفاده میکنید، Laravel امکان استفاده از Viewهای سازگار با Tailwind را نیز فراهم میکند.
# استفاده از Pagination در جدولهای مدیریتی
یکی از رایجترین کاربردهای Pagination در Laravel، جدولهای مدیریتی است.
فرض کنید جدول سفارشها را داریم:
$orders = Order::query()
->latest()
->paginate(25)
->withQueryString();
سپس:
<table>
<thead>
<tr>
<th>شماره سفارش</th>
<th>مبلغ</th>
<th>وضعیت</th>
</tr>
</thead>
<tbody>
@foreach ($orders as $order)
<tr>
<td>{{ $order->id }}</td>
<td>{{ $order->total }}</td>
<td>{{ $order->status }}</td>
</tr>
@endforeach
</tbody>
</table>
{{ $orders->links() }}
اگر فیلترهایی هم روی جدول داشته باشیم، withQueryString() کمک میکند هنگام جابهجایی صفحات، فیلترها از بین نروند.
# Pagination در دادههای بزرگ
Pagination معمولی همیشه بهترین انتخاب برای دیتاستهای بسیار بزرگ نیست.
فرض کنید جدولی با دهها میلیون رکورد دارید.
در چنین شرایطی نوع Query، Indexها، روش مرتبسازی و ساختار دیتابیس اهمیت بسیار زیادی پیدا میکنند.
برای چنین سناریوهایی ممکن است cursorPaginate() انتخاب مناسبتری باشد.
مثلاً:
$orders = Order::orderBy('id')
->cursorPaginate(50);
در پروژههای بزرگ، انتخاب روش Pagination باید بر اساس ساختار واقعی دادهها و Query انجام شود.
# Pagination و Index دیتابیس
فرض کنید Query شما به شکل زیر است:
Order::where('status', 'paid')
->orderBy('created_at', 'desc')
->paginate(20);
اگر جدول بسیار بزرگ باشد، Index مناسب میتواند روی عملکرد Query تأثیر قابلتوجهی داشته باشد.
به همین دلیل Pagination را نباید کاملاً جدا از طراحی دیتابیس در نظر گرفت.
موضوع Index و بهینهسازی Query در مقالات تخصصیتر این کلاستر بررسی خواهد شد.
# اشتباهات رایج در Pagination
1. دریافت تمام اطلاعات قبل از Pagination
این کار اشتباه است:
$users = User::all();
$users = $users->forPage(1, 20);
چون ابتدا تمام دادهها دریافت شدهاند.
بهتر است Pagination را مستقیماً در Query انجام دهید:
$users = User::paginate(20);
2. استفاده از تعداد بیش از حد برای هر صفحه
مثلاً:
User::paginate(5000);
در بسیاری از پروژهها منطقی نیست.
تعداد آیتمها باید متناسب با نوع صفحه و نیاز واقعی کاربر باشد.
3. از بین رفتن فیلترها هنگام تغییر صفحه
اگر URL شامل فیلترهاست:
/products?category=5&status=active
ولی هنگام رفتن به صفحه دوم فیلترها از بین میروند، تجربه کاربری نامناسب خواهد شد.
در چنین شرایطی:
->withQueryString()
را بررسی کنید.
4. استفاده اشتباه از Pagination برای پردازش Batch
Pagination برای نمایش دادهها به کاربر طراحی شده است.
اگر هدف شما پردازش میلیونها رکورد در یک Job یا Command باشد، معمولاً ابزارهایی مانند chunk()، chunkById() یا روشهای Lazy مناسبتر هستند.
# Pagination یا Chunk؟
این دو مفهوم را نباید با یکدیگر اشتباه گرفت.
Pagination بیشتر برای نمایش دادهها به کاربر است:
User::paginate(20);
اما Chunk برای پردازش حجم زیادی از دادهها در بخشهای کوچکتر کاربرد دارد:
User::chunk(1000, function ($users) {
foreach ($users as $user) {
// پردازش
}
});
مثلاً:
| نیاز | روش مناسب |
|---|---|
| نمایش کاربران به کاربر | Pagination |
| صفحه محصولات فروشگاه | Pagination |
| جدول پنل مدیریت | Pagination |
| پردازش میلیونها کاربر | Chunk |
| اجرای عملیات روی همه رکوردها | Chunk / Lazy |
| حرکت ترتیبی روی دیتاست بزرگ | Cursor Pagination |
# یک مثال کامل و کاربردی
فرض کنید صفحهای برای لیست سفارشهای یک فروشگاه داریم.
میخواهیم:
- فقط سفارشهای پرداختشده را نمایش دهیم.
- جدیدترین سفارشها ابتدا نمایش داده شوند.
- در هر صفحه 25 سفارش داشته باشیم.
- فیلترهای URL حفظ شوند.
Controller:
public function index()
{
$orders = Order::query()
->where('status', 'paid')
->latest()
->paginate(25)
->withQueryString();
return view('orders.index', compact('orders'));
}
View:
<h1>سفارشهای پرداختشده</h1>
<table>
<thead>
<tr>
<th>شماره</th>
<th>مبلغ</th>
<th>تاریخ</th>
</tr>
</thead>
<tbody>
@foreach ($orders as $order)
<tr>
<td>{{ $order->id }}</td>
<td>{{ number_format($order->total) }}</td>
<td>{{ $order->created_at }}</td>
</tr>
@endforeach
</tbody>
</table>
{{ $orders->links() }}
این الگو در بسیاری از پروژههای واقعی Laravel قابل استفاده است.
# بهترین روش برای انتخاب نوع Pagination
برای انتخاب روش مناسب میتوان از این الگو استفاده کرد:
پروژه معمولی
Model::paginate(20);
فقط قبلی و بعدی
Model::simplePaginate(20);
دیتاست بسیار بزرگ یا حرکت ترتیبی
Model::orderBy('id')->cursorPaginate(20);
اما انتخاب نهایی باید بر اساس Query، Indexها، تعداد دادهها و نیاز رابط کاربری انجام شود.
# نکات مهم برای Pagination حرفهای
برای داشتن Pagination مناسب در پروژه Laravel، موارد زیر را در نظر بگیرید:
- Pagination را مستقیماً روی Query اعمال کنید.
- از دریافت همه رکوردها با
all()یاget()قبل از Pagination خودداری کنید. - تعداد آیتمهای هر صفحه را منطقی انتخاب کنید.
- در Search و Filter از
withQueryString()استفاده کنید. - برای دیتاستهای بسیار بزرگ، Cursor Pagination را بررسی کنید.
- Queryهای Pagination را با Indexهای مناسب دیتابیس هماهنگ کنید.
- در APIها حداکثر تعداد آیتمهای قابل درخواست را محدود کنید.
- برای پردازش Batch از Pagination به جای ابزارهای مناسب پردازش داده استفاده نکنید.
# جمعبندی
Pagination در Laravel یکی از امکانات مهم برای مدیریت و نمایش حجم زیادی از دادهها است.
به جای اینکه تمام رکوردهای دیتابیس را یکجا دریافت کنیم، میتوانیم آنها را به صفحات کوچکتر تقسیم کنیم.
سادهترین حالت استفاده از Pagination در Eloquent به شکل زیر است:
$products = Product::paginate(20);
و برای Query Builder:
$products = DB::table('products')->paginate(20);
سپس در Blade:
{{ $products->links() }}
Laravel همچنین روشهای دیگری مانند:
paginate()
simplePaginate()
cursorPaginate()
را در اختیار ما قرار میدهد.
برای بیشتر صفحات معمولی، paginate() انتخاب مناسبی است؛ در حالی که simplePaginate() برای Pagination ساده و cursorPaginate() برای برخی دیتاستهای بزرگ و پیمایش ترتیبی گزینههای مناسبتری هستند.
مهمتر از همه، Pagination فقط یک قابلیت ظاهری برای تقسیم صفحات نیست. اگر پروژه شما با حجم زیادی از داده کار میکند، نحوه طراحی Query، Indexهای دیتابیس، تعداد رکوردهای هر صفحه و روش انتخابشده میتوانند روی Performance سیستم تأثیر زیادی داشته باشند.
# سوالات متداول
Pagination در Laravel چیست؟
Pagination روشی برای تقسیم دادههای زیاد به صفحات کوچکتر است تا در هر درخواست فقط تعداد مشخصی رکورد نمایش داده شود.
چگونه در Laravel Pagination ایجاد کنیم؟
با Eloquent:
$users = User::paginate(20);
و با Query Builder:
$users = DB::table('users')->paginate(20);
چگونه لینک Pagination را در Blade نمایش دهیم؟
کافی است بنویسید:
{{ $users->links() }}
تفاوت paginate و simplePaginate چیست؟
paginate() اطلاعات کاملتری برای ساخت Pagination و صفحات در اختیار میگذارد، در حالی که simplePaginate() برای حرکت ساده بین صفحه قبلی و بعدی مناسب است.
cursorPaginate در Laravel چیست؟
cursorPaginate() نوعی Pagination مبتنی بر Cursor است که برای برخی سناریوهای دیتاست بزرگ میتواند نسبت به Pagination مبتنی بر شماره صفحه مناسبتر باشد.
چگونه فیلترها را هنگام Pagination حفظ کنیم؟
میتوانید از:
->withQueryString()
استفاده کنید.
مثلاً:
$products = Product::paginate(20)
->withQueryString();
آیا Pagination فقط برای Eloquent است؟
خیر. Query Builder نیز از Pagination پشتیبانی میکند:
DB::table('users')->paginate(20);
آیا Pagination باعث سریع شدن همه Queryها میشود؟
خیر. Pagination مقدار دادهای را که برای یک صفحه دریافت میشود محدود میکند، اما عملکرد نهایی به Query، Indexها، دیتابیس و ساختار دادهها نیز وابسته است.





