راهنمای ساخت مدیریت پایه
در این راهنما، به صورت مرحله به مرحله یاد میگیریم که چگونه با اس تفاده از Doravel و ساختار ماژولار آن، یک مدیریت ساده و کامل (CRUD) بسازیم.
هدف این است که حتی اگر برای اولین بار با Doravel کار میکنید، بتوانید به راحتی یک ماژول عملی راهاندازی کنید.
۱. ساخت ماژول
ابتدا یک ماژول جدید برای بخش مدیریت ایجاد کنید:
php artisan module:make Bank
۲. تنظیم مسیرها (Routes)
پاک کردن مسیرهای پیشفرض
داخل فایلهای زیر، Route های پیشفرض برای ماژول را پاک کنید.
MODULE_NAME/routes/web.phpMODULE_NAME/routes/api.php
به جای MODULE_NAME نام ماژول خود را قرار دهید.
پیکربندی RouteServiceProvider
در Service Provider ماژول، مسیرها تعریف شده است، prefix و as و middleware را تنظیم کنید.
مسیرهای وب
Route::middleware(['web', 'authorized'])
->prefix('admin/basic')
->as('admin.basic.')
->group(module_path('Bank', '/routes/web.php'));
مسیرهای API
در صورتی که در ماژول خود به API نیاز دارید، مسیرهای API را نیز به صورت زیر تنظیم کنید.
Route::middleware(['web', 'authorized'])
->prefix('admin/api/basic')
->as('admin.api.basic.')
->group(module_path('Bank', '/routes/api.php'));
حتما middleware با نام authorized را اضافه کنید تا فقط کاربران وارد شده یا احرازهویت شده در پروژه بتوانند به این مسیرها دسترسی داشته باشند.
در نظر داشته باشید که middleware با نام authorized در پکیج Doravel وجود دارد و نیاز به تعریف مجدد در پروژه نیست.
۳. ساخت کنترلر
یک کنترلر جدید شامل صفحات و عملیات های یک مدیریت کامل که با ساختار Resource است، ایجاد کنید:
class BankController extends Controller
{
public function index() {}
public function create() {}
public function store(Request $request) {}
public function show(Bank $bank) {}
public function edit(Bank $bank) {}
public function update(Request $request, Bank $bank) {}
public function destroy(Bank $bank) {}
}
۴. تعریف مسیرهای CRUD
تمامی Route های مربوط به CRUD را در فایل web.php تعریف کنید.
Route::prefix('banks')->as('banks.')->controller(BankController::class)->group(function () {
Route::get('/', 'index')
->name('index')
->title('لیست بانک ها')
->showInSidebar();
Route::get('create', 'create')
->name('create')
->title('درج بانک')
->parentRoute('admin.basic.banks.index');
Route::post('store', 'store')->name('store');
Route::prefix('{bank}')->group(function () {
Route::get('show', 'show')
->name('show')
->title('جزئیات بانک')
->parentRoute('admin.basic.banks.index');
Route::get('edit', 'edit')
->name('edit')
->title('ویرایش بانک')
->parentRoute('admin.basic.banks.index');
Route::put('update', 'update')->name('update');
Route::delete('destroy', 'destroy')->name('destroy');
});
});
بهتر است برای title مربوط به Route ها، از ترجمه استفاده کنید تا در صورت نیاز به چندزبانه شدن، راحت تر بتوانید این کار را انجام دهید
(نیاز به استفاده از تابع کمکی ترجمه ()__ نیست و میتوانید به صورت مستقیم کلید ترجمه را قرار دهید).
چون برای این مدیریت API تعریف نداریم، فایل api.php خالی میماند.
۵. تعریف منو با Doravel
در سایدبار پنل مدیریت، منوها به صورت خودکار از روی Route های تعریف شده ساخته میشوند.
اما اگر بخواهید سطح های بالاتر از Route ها را نیز در منو داشته باشید، باید آنها را به صورت دستی تعریف کنید.
برای اینکار باید یک متد تعریف کنید و این متد را داخل boot در ServiceProvider اصلی ماژول صدا بزنید.
use Dornica\Foundation\Doravel\Facade\Doravel;
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuGroup;
use Dornica\Foundation\RoutePropertyCollector\MenuCollector\Builders\MenuSubgroup;
public function registerMenuGroup(): void
{
Doravel::menu(function () {
return [
MenuGroup::make()
->name('admin.basic')
->title("اطلاعات پایه")
->icon('fa-regular fa-memo-circle-info')
->subMenu([
MenuSubgroup::make()
->name('admin.basic.banks')
->title('لیست بانک ها'),
]),
];
});
}
۶. ساخت و مدیریت مدلها
در پروژههای مبتنی بر Doravel، معمولاً در ابتدای پروژه، تمامی مدلها یکبار و بهصورت کامل ساخته میشوند. بنابراین در اغلب موارد فایل مدل از قبل وجود دارد و فقط باید به داخل ماژول منتقل شود.
حالت اول: مدل از قبل در پروژه وجود دارد (سناریوی رایج)
در ابتدای پروژه همه مدلها ساخته شدهاند و در مسیر زیر قرار دارند:
app/Models
برای مثال مدل بانک در مسیر زیر قرار دارد:
app/Models/Bank.php
از آنجا که ساختار پروژه ما ماژولار است، نباید مدلها در ریشه پروژه باقی بمانند. مدل مربوطه را به داخل ماژول منتقل کنید:
MODULE_PATH/app/Models/Bank.php
بعد از انتقال، حتماً namespace مدل را با ساختار ماژول اصلاح کنید.
namespace Modules\Bank\Models;
حالت دوم: مدل وجود ندارد (سناریوی استثنایی)
اگر به هر دلیلی مدل مورد نظر در پروژه وجود نداشت، فقط همان مدل را بسازید.
مثلاً برای جدول banks:
php artisan code:models --table=banks
یا مثال دیگر:
php artisan code:models --table=users
پس از ساخته شدن مدل در مسیر:
app/Models
دقیقاً مانند حالت اول، مدل را به داخل ماژول منتقل کنید:
MODULE_PATH/app/Models
و namespace آن را اصلاح نمایید.
نکات مهم
- پکیج Reliese دیتابیس متصل به پروژه را بررسی میکند و مدلها را بر اساس ساختار واقعی جداول میسازد.
- در Doravel، وجود مدل در ماژول الزامی است و نگه داشتن آن در ریشه پروژه اشتباه معماری محسوب میشود.
- ساخت مجدد مدلها برای هر ماژول کار درستی نیست. یکبار بساز، بعد فقط منتقل کن.
منبع پکیج
برای اطلاعات بیشتر در مورد پکیج ساخت مدل:
۷. ایجاد فایلهای Blade
چهار فایل اصلی برای CRUD بسازید:
MODULE_PATH/resources/views/index.blade.php
MODULE_PATH/resources/views/show.blade.php
MODULE_PATH/resources/views/create.blade.php
MODULE_PATH/resources/views/edit.blade.php
۸. صفحه لیست (Index)
Table Generator
ساخت Table Generator با استفاده از کامند زیر:
php artisan dornica:make-table BankTable --module=Bank
برای مشاهده جزئیات بیشتر در مورد Table Generator میتوانید به مستندات مربوطه مراجعه کنید: مستندات Table Generator
کنترلر
استفاده از Table Generator و نمایش در ویو:
use Dornica\PanelKit\BladeLayout\Facade\BladeLayout;
use Modules\Bank\Generators\Tables\BankTable;
public function index()
{
BladeLayout::table(BankTable::class);
return view('bank::index');
}
ویو
نمایش یا Render کردن Table Generator در ویو:
<x-default-layout>
{!! bladeLayout()->table()->render() !!}
</x-default-layout>
۹. صفحه درج (Create)
کنترلر
نمایش فرم درج:
public function create()
{
return view('bank::create');
}
اعتبارسنجی ورودیها
استفاده از Form Request برای اعتبارسنجی ورودیها:
class StoreBankRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'code' => ['required', 'string', 'max:50', 'unique:banks,code'],
];
}
}
برای استفاده در Form Validation باید در انتهای Blade ویو، اسکریپت اعتبارسنجی را اضافه کنید.
@push("scripts")
@canAccess('admin.basic.banks.store')
{!! FormValidator::formRequest(Modules\Bank\Http\Requests\StoreBankRequest::class, "#create-bank") !!}
@endcanAccess
@endpush
توجه کنید که create-bank# باید همان id فرم و StoreBankRequest باید همان نام کلاس Form Request باشد.
ویو
پیاده سازی فرم درج با استفاده از کامپوننتهای Blade:
برای ساخت فرمها میتوانید از مجموعه کامپوننتهای فرم مثل text-input و select و radio-group و سایر موارد مشابه استفاده کنید:
مستندات کامپوننتهای فرم
<x-default-layout>
<x-card :title="getPageTitle()">
<form
id="create-bank"
action="{{ route("admin.basic.banks.store") }}"
method="post"
>
@csrf
<div class="row g-3">
<x-text-input
containerClass="col-md-6"
name="name"
:label="__('base::general.name')"
:value="old('name')"
/>
<x-text-input
containerClass="col-md-6"
name="code"
:label="__('base::general.code')"
:value="old('code')"
direction="ltr"
/>
</div>
@canAccess('admin.basic.banks.store')
<div class="card-footer pb-0 px-0 d-flex gap-4 pt-5 justify-content-end mt-4">
<x-reset-button
:title="__('base::general.reset')"
variant="light"
appearance="outline"
/>
<x-button
buttonType="submit"
:title="__('base::general.submit')"
/>
</div>
@endcanAccess
</form>
</x-card>
@push("scripts")
@canAccess('admin.basic.banks.store')
{!! FormValidator::formRequest(Modules\Bank\Http\Requests\StoreBankRequest::class, "#create-bank") !!}
@endcanAccess
@endpush
</x-default-layout>
ذخیره اطلاعات
پیاده سازی منطق ذخیره اطلاعات در کنترلر:
public function store(StoreBankRequest $request)
{
$inputs = $request->validated();
$inputs['sort'] = getNextSortValue(Bank::class);
try {
Bank::create($inputs);
return redirect()
->route('admin.basic.banks.index')
->withFlash(
message: __("base::message.create_successfully"),
type: 'success',
);
} catch (Exception $exception) {
Log::error($exception);
return back()
->withFlash(
message: __("base::message.error_occurred"),
type: 'error',
);
}
}