پرش به مطلب اصلی

Route Property Collector

Route Property Collector سیستمی برای جمع‌آوری و مدیریت متادیتای Route ها در پروژه است.

با استفاده از این سیستم می‌توان اطلاعاتی مانند:

  • عنوان صفحه
  • نمایش در سایدبار
  • آیکون
  • Badge
  • سطح دسترسی
  • روابط والد–فرزند

را مستقیماً در تعریف Route مشخص کرد و سپس به‌صورت ساختاریافته در پنل استفاده نمود.

ذهنیت کلی (قبل از هر چیز این را بخوانید)

این سیستم را با دو لایه ببینید؛ همین دو نکته کلیدِ فهمِ کلِ صفحه است:

  1. متادیتای هر Route — روی تک‌تکِ Route ها با ماکروهایی مثل ->title()، ->icon()، ->showInSidebar() گذاشته می‌شود.
  2. درختِ خودکار — سیستم از روی نامِ (name) Route ها یک درختِ «دسته‌بندی ← زیر‌دسته ← آیتم» می‌سازد. مثلاً نامِ admin.users.index یعنی: دسته‌ی admin.users و آیتمِ index.

پس نقطه (.) در نامِ Route = یک سطح در درخت. هر جای این صفحه (به‌ویژه بخشِ منو) گیج شدید، به همین قانون برگردید.


قابلیت‌ها

جمع‌آوری خودکار متادیتا

متادیتاهای تعریف‌شده در Route ها به‌صورت خودکار جمع‌آوری و سازماندهی می‌شوند.

پشتیبانی از کش

برای بهبود کارایی، اطلاعات Route ها و Breadcrumb ها می‌توانند کش شوند.

منابع چندگانه

متادیتاها می‌توانند از دو منبع خوانده شوند:

  • local
  • database

تولید خودکار Breadcrumb

بر اساس نام Route ها و روابط والد–فرزند، ساختار Breadcrumb به‌صورت خودکار ساخته می‌شود.


ماکروهای Route

سیستم Route Property مجموعه‌ای از ماکروها برای تعریف متادیتای Route ارائه می‌دهد.

عنوان صفحه

جهت استفاده در Breadcrumb و Sidebar و قسمت های مورد نیاز

Route::get('/users', [UserController::class, 'index'])
->name('admin.users.index')
->title('مدیریت کاربران');

نمایش در سایدبار

Route::get('/users', [UserController::class, 'index'])
->name('admin.users.index')
->showInSidebar();

آیکون

جهت تعریف آیکون موجود در سایدبار

Route::get('/users', [UserController::class, 'index'])
->name('admin.users.index')
->icon('fa-users');

Badge

نمایش Badge در Sidebar (اگر null باشد، نمایش داده نمی شود)

مقدار ثابت
->badge(10, 'danger')
مقدار پویا
->badge(function () {
return User::count();
}, 'primary')

سطح دسترسی

بررسی دسترسی کاربر، هنگام استفاده

->permission('user.view')

Visibility

وضعیت نمایش در Sidebar

مقدار ثابت
->visibility(false)
مقدار پویا
->visibility(function ($user) {
return $user->isAdmin();
})

مرتب‌سازی (Sort)

برای تعیین ترتیب نمایش آیتم‌ها در Sidebar می‌توانید از ماکروی sort استفاده کنید. عدد کمتر یعنی نمایش زودتر.

نمونه روی Route
Route::get('/users', [UserController::class, 'index'])
->name('admin.users.index')
->title('مدیریت کاربران')
->showInSidebar()
->sort(10);
نمونه روی MenuGroup / MenuSubgroup
MenuGroup::make()
->name('admin.customer')
->title('مشتریان')
->sort(20)
->subMenu([
MenuSubgroup::make()
->name('admin.customer.wallet')
->title('کیف پول')
->sort(5),
]);

Route های الحاقی

برای مرتبط کردن چند Route به زیر مجموعه یک Route اصلی استفاده می‌شود.

->appendRoutes([
'admin.users.create',
'admin.users.edit',
])

Route والد

برای عملکرد Breadcrumb و Sidebar و مشخص کردن زیرمجموعه هر Route

->parentRoute('admin.users.index')

عدم ایندکس شدن

با اعمال این ماکرو، اگر Source بر روی Database تنظیم شده باشد، در نظر گرفته نمی شود

->skipIndexing()

بدون بررسی دسترسی

در صورت استفاده، در سیستم کنترل دسترسی در نظر گرفته نمی شود و همه کاربران به آن دسترسی خواهند داشت

->withoutPermission()

استفاده از Route Property Collector

دریافت Collector

$collector = routePropertyCollector();

دریافت عنوان صفحه

عنوان Route فعلی
$title = $collector->getPageTitle();
عنوان یک Route خاص
$title = $collector->getPageTitle('admin.users.index');

// helper function
$title = getPageTitle('admin.users.index');

دریافت Route ها

$routes = $collector->getRoutes();
نمونه خروجی
[
'users' => [
'name' => 'کاربران',
'slug' => 'users',
'permissions' => [...],
'subcategories' => [...],
]
]

دریافت Breadcrumb

$breadcrumb = $collector->breadcrumb('admin.users.permissions.edit');
نمونه خروجی
[
[
'level' => 1,
'name' => 'کاربران',
'slug' => 'users',
'visibility' => true
],
[
'level' => 2,
'name' => 'سطوح دسترسی',
'slug' => 'users.permissions',
'visibility' => true
],
[
'level' => 3,
'name' => 'ویرایش دسترسی',
'slug' => 'users.permissions.edit',
'show_in_sidebar' => false,
'visibility' => true
]
]

Blade Directive

عنوان صفحه

<title> @title </title>

تعریف گروه و زیرگروه‌ در سایدبار

سیستم منوی سایدبار یک API زنجیره‌ای و روان برای ساخت منوهای ناوبری ساختاریافته در پنل‌ را فراهم می‌کند. این سیستم به شما امکان می‌دهد گروه‌های منو و زیرگروه‌ها را با پشتیبانی از نشان‌ها (Badge)، آیکون‌ها، مسیرها (Route) و روابط والد-فرزند تعریف کنید.
به صورت پیش فرض ویژگی Route ها را هنگام تعریف هر Route میتوان تعیین کرد، در ادامه به این می پردازیم که چطور گروه و زیرگروه Route هارا برای سایدبار مشخص کنیم.

منو دقیقاً برای چیست؟

تا اینجا دیدیم هر Route با ->title() عنوان می‌گیرد. اما دسته‌بندی‌ها و زیر‌دسته‌ها Route مستقل ندارند که رویشان ->title() بگذارید — این‌ها فقط پیشوندِ مشترکِ چند Route اند (مثلاً admin.users پیشوندِ admin.users.index و admin.users.create است).

پس عنوان/آیکون/ترتیبِ همین پیشوندها را با MenuGroup/MenuSubgroup می‌دهید:

چه چیزی؟عنوانش از کجا می‌آید؟
آیتمِ برگ (خودِ یک Route، مثل admin.users.create)->title() روی همان Route
دسته / زیر‌دسته (پیشوند، مثل admin.users)MenuGroup / MenuSubgroup
قانون طلایی نام‌گذاری (مهم‌ترین نکته)

مقدارِ ->name() در MenuGroup/MenuSubgroup باید دقیقاً برابر با نامِ همان دسته/زیر‌دسته باشد؛ یعنی همان پیشوندِ کاملِ Route ها، به‌همراهِ پیشوندِ ابتدایی مثل admin.

اگر نام را ناقص بگذارید (مثلاً filter-templates به‌جای admin.export.filter-templates)، به هیچ دسته‌ای وصل نمی‌شود و عنوان اعمال نمی‌گردد.

مثالِ عملی — فرض کنید این Route ها را دارید:

// admin.export.generate admin.export.download
// admin.export.filter-templates.index ...store ...destroy

درختِ خودکار می‌شود: دسته‌ی admin.export ← زیر‌دسته‌ی admin.export.filter-templates ← آیتم‌های برگ. برای فارسی‌کردنِ عنوانِ دسته و زیر‌دسته، نامِ MenuGroup/MenuSubgroup را برابرِ همان پیشوندها بگذارید:

MenuGroup::make()
->name('admin.export') // = نامِ دسته
->title('گزارش‌گیری')
->subMenu([
MenuSubgroup::make()
->name('admin.export.filter-templates') // = نامِ زیر‌دسته (کامل، نه فقط filter-templates)
->title('قالب‌های فیلتر'),
]);

آیتم‌های برگ (admin.export.generate و …) عنوانشان از ->title() خودِ Route می‌آید — منو رویشان اثری ندارد.

کلاس‌های سازنده (Builders)

نماینده یک گروه منوی اصلی است که می‌تواند شامل چندین زیرگروه باشد
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuGroup;

MenuGroup::make()
->name('admin.dashboard')
->title('داشبورد')
->icon('fa-solid fa-home')
->route('admin.dashboard.index')
->badge(5);
نماینده یک زیرمنو است که می‌تواند زیر یک MenuGroup قرار گیرد
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuSubgroup;

MenuSubgroup::make()
->name('admin.users.managers')
->title('مدیران')
->route('admin.users.managers.index')
->parent('admin.users');
هشدار

اگر برای MenuSubgroup از parent(...) استفاده می‌کنید، مقدار parent باید حتماً قبلاً به عنوان MenuGroup از طریق Doravel::menu(...) در یک Service Provider ثبت شده باشد. در غیر این صورت، هنگام collect شدن منوها exception دریافت می‌کنید.

تعریف منو در AppServiceProvider

منو ها و زیر منو ها را به دو صورت می توان تعریف کرد.

نوع اول - متمرکز
use Dornica\Foundation\Doravel\Facade\Doravel;
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuGroup;
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuSubgroup;

public function sidebarMenu(): void
{
Doravel::menu(function () {
return [
// group
MenuGroup::make()
->name('admin.customer')
->title('مشتریان')
->icon('fa-solid fa-code')
->badge(function () {
return 6;
})
->subMenu([
// subgroup
MenuSubgroup::make()
->name('admin.customer.wallet')
->title('کیف پول')
->badge(function () {
return 2;
}),
// subgroup
MenuSubgroup::make()
->name('admin.customer.factor')
->title('فاکتور ها')
->badge(function () {
return 2;
}),
]),
];
});
}
نوع دوم - مستقل
use Dornica\Foundation\Doravel\Facade\Doravel;
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuGroup;
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuSubgroup;

public function sidebarMenu(): void
{
// group
Doravel::menu(function () {
return [
MenuGroup::make()
->name('admin.customer')
->title('مشتریان')
->icon('fa-solid fa-code')
->badge(function () {
return 6;
})
];
});

// subgroup
Doravel::menu(function () {
return [
MenuSubgroup::make()
->name('admin.customer.wallet')
->title('کیف پول ها')
->badge(function () {
return 2;
}),
MenuSubgroup::make()
->name('admin.customer.factor')
->title('فاکتور ها')
->badge(function () {
return 2;
}),
];
});
}
نکته

در نوع دوم میتواید هر کدام از Doravel::menu ها را جداگانه در Service Provider های مختلف قرار دهید.

کاهش کد تکراری با parent

برای اینکه در ماژول‌های مختلف، تعریف گروه‌های منو را تکرار نکنید:

  1. یک MenuGroup مرکزی (مثلاً base) را فقط یک‌بار در Service Provider ثبت کنید.
  2. در هر ماژول، فقط MenuSubgroup‌ها را تعریف کنید و با parent('base') به آن وصل کنید.
ثبت یک‌باره گروه اصلی
Doravel::menu(function () {
return [
MenuGroup::make()
->name('base')
->title('اطلاعات پایه'),
];
});
استفاده در هر ماژول بدون تکرار گروه
Doravel::menu(function () {
return [
MenuSubgroup::make()
->name('settings.region')
->title('تنظیمات مناطق')
->parent('base'),
];
});

نکته: قبل از parent('base')، حتماً base را به عنوان MenuGroup ثبت کنید.

ویژگی‌های کلیدی

نام‌گذاری سلسله مراتبی

سیستم به طور خودکار می‌تواند روابط والد-فرزند را بر اساس نام‌ها تشخیص دهد:

MenuSubgroup::make()
->name('admin.samples.components')
->title('کامپوننت ها');

این زیرگروه به طور خودکار زیر گروه admin.samples قرار می‌گیرد.

نشان‌های پویا (Badge)

// مقدار ثابت
->badge(12, 'primary')

// مقدار پویا
->badge(function () {
return User::count();
}, 'danger', [$arg1, $arg2])

متدهای قابل استفاده

متدتوضیحنمونه
name(string $name)تعیین شناسه یکتاname('admin.users')
title(string $title)تعیین عنوان نمایشیtitle('TITLE')
sort(int $sort)تعیین ترتیب نمایشsort(10)
icon(string $icon)تعیین آیکون (فقط MenuGroup)icon('fa-users')
route(string $routeName)تعیین مسیرroute('admin.users.index')
badge(mixed $value, string $style)تعیین نشانbadge(5, 'danger')
subMenu(array $subMenus)تعیین زیرمنوها (فقط MenuGroup)subMenu([$sub1, $sub2])
subMenu(array $subMenus)تعیین زیرمنوها (فقط MenuSubgroup)subMenu([$sub1, $sub2])

نکات مهم

  1. هر گروه منو می‌تواند چندین زیرگروه داشته باشد
  2. زیرگروه‌ها می‌توانند به صورت مستقل تعریف شوند
  3. سیستم به طور خودکار زیرگروه‌های مستقل را با گروه‌های والد تطبیق می‌دهد
  4. برای عملکرد صحیح، باید از Doravel::menu در AppServiceProvider استفاده شود
  5. مقادیر badge می‌توانند ثابت، پویا (کالبک) یا قابل سریال‌سازی باشند

نمونه کامل

// روش اول: تعریف گروه با زیرمنوها
MenuGroup::make()
->name('admin.products')
->title('محصولات')
->icon('fa-box')
->subMenu([
MenuSubgroup::make()
->name('admin.products.list')
->title('لیست محصولات')
->route('admin.products.index'),
]);

// روش دوم: تعریف جداگانه
MenuSubgroup::make()
->name('admin.products.categories')
->title('دسته‌بندی‌ها')
->parent('admin.products')
->badge(Category::count(), 'info');

مدیریت کش

پاک کردن کش عنوان‌ها

routePropertyCollector()->forgetCachedTitles();

پاک کردن کش Breadcrumb

routePropertyCollector()->forgetCachedBreadcrumb();

بررسی وجود کش

$hasCache = routePropertyCollector()->hasCachedTitle('admin.users.index');

استفاده پیشرفته

دریافت فقط Route های سایدبار

$collector->onlySidebar()->getRoutes();

بررسی روابط Route

بررسی فرزند بودن
$collector->isChildOf(
'admin.users.permissions.edit',
'admin.users'
);
بررسی والد بودن
$collector->isParentOf(
'admin.users',
'admin.users.permissions.edit'
);

نکات مهم

  1. برای استفاده از کش، گزینه should_cache_route باید فعال باشد.
  2. customFilter با کش سازگار نیست.
  3. onlySidebar با کش سازگار نیست.
  4. بعد از تغییر Route ها باید کش پاک شود.
  5. روابط والد–فرزند به‌صورت خودکار از نام Route ها تشخیص داده می‌شود.

مثال کامل

Route::prefix('admin')->name('admin.')->group(function () {

Route::get('/', [HomeController::class, 'index'])
->name('index')
->title('صفحه اصلی')
->showInSidebar()
->withoutPermission();

Route::prefix('users')->name('users.')->group(function () {

Route::get('/', [UserController::class, 'index'])
->name('index')
->title('لیست کاربران')
->showInSidebar()
->icon('fa-users')
->badge(function () {
return User::count();
}, 'primary');

Route::get('/create', [UserController::class, 'create'])
->name('create')
->title('ایجاد کاربر جدید')
->parentRoute('admin.users.index');

});
});