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

Import (درج گروهی داده‌ها)

Import یک ابزار قدرتمند برای مدیریت فرآیند وارد کردن داده‌ها از فایل و استفاده از پیکربندی‌های سفارشی است. استفاده صحیح از این قابلیت می‌تواند به شما در ساده‌سازی روند عملیات داده‌های وارد شده کمک کند.


ایجاد Import

برای ایجاد یک کلاس import جدید که از BaseImport ارث‌بری می‌کند:

php artisan dornica:make-import SampleImport

برای ایجاد import داخل ماژول مشخص:

php artisan dornica:make-import SampleImport --module=MODULE_NAME
نکته

مقدار MODULE_NAME باید نام ماژول شما باشد.

این کلاس به شما امکان می‌دهد تا ساختار import را تعریف کنید و ویژگی‌های مختلف آن را سفارشی کنید.

مثال زیر، ساختار کلی یک کلاس import را نشان می‌دهد:

<?php

namespace App\Generators\Imports;

use Dornica\Foundation\Import\BaseImport;

class BookImport extends BaseImport
{
public function __construct()
{
$this
->setFileFormat(FileFormat::EXCEL)
->setTitle('درج گروهی کتاب ها')
->setIndexRoute('books.index')
->setPerPage(15);
}

public function fields(): array
{
// تعریف فیلدها
}

public function inputs(): array
{
// تعریف ورودی های فرم درج گروهی
}

public function beforeProcessRows(ImportResult $result): void
{
// بررسی شرایط لازم قبل از درج داده ها
}

public function processRows(ImportResult $result): void
{
// پردازش داده ها
}
}

تنظیمات اولیه

می‌توانید تنظیمات پایه را داخل constructor انجام دهید:

public function __construct()
{
$this
->setFileFormat(FileFormat::EXCEL) // تنظیم فرمت فایل که میتواند excel یا csv باشد
->setTitle('درج گروهی کتاب ها') // تنظیم عنوان فرم
->setIndexRoute('books.index') // نام route صفحه لیست (اختیاری)
->setPerPage(15); // تعداد ردیف در هر صفحه پیش‌نمایش (پیش‌فرض: ۱۵)
}

دکمه بازگشت به لیست (setIndexRoute)

با setIndexRoute می‌توانید نام route صفحه لیست را روی کلاس import تعریف کنید تا در فرم درج گروهی دکمه «بازگشت به لیست» نمایش داده شود.

$this->setIndexRoute('books.index');
نکته
  • مقدار باید نام route باشد (مثل 'books.index')، نه URL؛ از route('books.index') استفاده نکنید.
  • اگر setIndexRoute تعریف شده باشد، همان اولویت دارد؛ در غیر این صورت از index_route ذخیره‌شده در session (مثلاً هنگام باز شدن import از جدول) به‌عنوان fallback استفاده می‌شود.
  • اگر هیچ‌کدام تنظیم نشده باشد، دکمه بازگشت نمایش داده نمی‌شود.

تعداد ردیف در هر صفحه پیش‌نمایش (setPerPage)

با setPerPage می‌توانید تعداد ردیف‌های نمایش‌داده‌شده در هر صفحه جدول پیش‌نمایش درج گروهی را تنظیم کنید. مقدار پیش‌فرض 15 است.

$this->setPerPage(25);
نکته
  • مقدار باید عدد صحیح بزرگ‌تر از صفر باشد؛ مقادیر کوچک‌تر از 1 به 1 محدود می‌شوند.
  • این تنظیم فقط صفحه‌بندی سمت کلاینت در مرحله پیش‌نمایش را کنترل می‌کند و روی پردازش نهایی import تأثیری ندارد.

دریافت تنظیمات (getters)

متدهای setter متناظر getter دارند و در UI، کنترلر یا منطق سفارشی قابل استفاده هستند:

$import = new BookImport();

$import->getFileFormat(); // FileFormat::EXCEL | FileFormat::CSV
$import->getTitle(); // «درج گروهی کتاب ها»
$import->getIndexRoute(); // 'books.index' یا null
$import->getPerPage(); // 15
مرجع متدهای عمومی BaseImport
متدتوضیح
setFileFormat / getFileFormatفرمت فایل (EXCEL یا CSV)
setTitle / getTitleعنوان فرم درج گروهی
setIndexRoute / getIndexRouteنام route دکمه بازگشت به لیست
setPerPage / getPerPageتعداد ردیف در هر صفحه پیش‌نمایش
fields()تعریف فیلدهای فایل (الزامی / abstract)
inputs()تعریف ورودی‌های فرم (اختیاری)
beforeProcessRows($result)بررسی شرایط قبل از درج
processRows($result)پردازش ردیف‌های معتبر
getFieldType($name)نوع فیلد به‌صورت enum ImportFieldType
getFieldTypeValue($name)نوع فیلد به‌صورت رشته (مثل 'text' یا 'link')
getFieldMappings()آرایه نگاشت فیلدها پس از setup
getFormInputs()آرایه ورودی‌های فرم پس از setup
getValidationRules()قوانین اعتبارسنجی جمع‌شده
getResourceKey()کلید یکتای منبع import
getImportDriver()درایور فعال (ExcelDriver / CSVDriver)
readFile($path, $previewMode = false)خواندن و پارس فایل
validateData($data, $cacheHash = null)اعتبارسنجی ردیف‌ها
getPreviewWithValidation($path, $inputValues = [])پیش‌نمایش همراه خطاهای اعتبارسنجی
preProcessRows($path, $inputValues = [])خواندن + اعتبارسنجی و ساخت ImportResult
execute($path, $inputValues = [], $selectedRowNumbers = null)اجرای کامل: preprocess → before → process
generateSampleFile()تولید فایل نمونه و بازگرداندن مسیر
render()رندر UI آمادهٔ import

مثال: اجرای مستقیم بدون Facade

$import = new BookImport();

$result = $import->execute(
filePath: $request->file('file')->getRealPath(),
inputValues: ['province_id' => 1],
selectedRowNumbers: [1, 2, 5], // فقط ردیف‌های انتخاب‌شده (۱‌پایه)
);

foreach ($result->getValidRows() as $row) {
// ...
}

مثال: فقط پیش‌پردازش (بدون درج)

$import = new BookImport();

$result = $import->preProcessRows(
$uploadedFile->getRealPath(),
['book_type' => 'digital']
);

$valid = $result->getValidRows();
$failed = $result->getFailedRows();

مثال: نوع فیلد به‌صورت رشته

if ($this->getFieldTypeValue('file') === 'link') {
// معادل ImportFieldType::LINK->value
}

تعریف فیلدها

در متد fields با استفاده از Builder با نام ImportField، می توان مشخص کرد که چه فیلدهایی باید از فایل وارد شوند.

class BookImport extends BaseImport
{
public function __construct()
{
$this
->setFileFormat(FileFormat::EXCEL)
->setTitle('درج گروهی کتاب ها')
->setIndexRoute('books.index')
->setPerPage(15);
}

public function fields(): array
{
return [
ImportField::make('title')
->column(0)
->label('عنوان')
->description('عنوان کتاب را وارد کنید')
->rules(['string', 'max:255'])
->sample([
'نمونه عنوان ۱',
'نمونه عنوان ۲',
'نمونه عنوان ۳',
]),

ImportField::make('author')
->column(1)
->label('نویسنده')
->description('نام نویسنده کتاب')
->rules(['string', 'max:255'])
->sample([
'نمونه نویسنده ۱',
'نمونه نویسنده ۲',
'نمونه نویسنده ۳',
]),

ImportField::make('isbn')
->column(2)
->label('ISBN')
->description('شابک بین‌المللی کتاب')
->rules(['string'])
->sample([
'ISBN1',
'ISBN2',
'ISBN3',
]),

ImportField::make('price')
->column(3)
->label('قیمت')
->rules(['numeric', 'min:0'])
->transform(function ($value) {
// Remove currency symbols and convert to float
return (float) preg_replace('/[^0-9.]/', '', $value);
})
->sample([
'1000.00',
'2000.00',
'3000.00',
]),

ImportField::make('published_at')
->column(4)
->label('تاریخ انتشار')
->rules(['nullable', 'date'])
->transform(function ($value) {
// Convert various date formats to Y-m-d
if (empty($value)) {
return null;
}
try {
return date('Y-m-d', strtotime($value));
} catch (Exception $e) {
return $value;
}
})
->sample([
'2024-01-01',
'2024-02-01',
'2024-03-01',
]),

ImportField::make('file')
->column(5)
->label('فایل')
->type(ImportFieldType::LINK)
->description('لینک فایل یا جلد کتاب')
->rules(['nullable', 'url'])
->sample('https://cdn.example.com/book-cover.jpg'),
];
}
}
پارامترهای ImportField Builder
  • column: ایندکس ستون فایل یا نام هدر (اختیاری)
    • فقط برای نگاشت خواندن فایل و جایگذاری در فایل نمونه است؛ ترتیب جدول پیش‌نمایش را تغییر نمی‌دهد.
    • مقدار عددی (۰‌پایه) موقعیت ستون در Excel/CSV را مشخص می‌کند؛ مثلاً 0 = ستون A، 1 = ستون B.
    • مقدار رشته‌ای نام هدر فایل است و برای match با عنوان ستون استفاده می‌شود.
    • اگر تعریف نشود، معمولاً با label به‌عنوان نام هدر match می‌شود.

  • label: عنوان قابل مشاهده
    • این عنوان برای نمایش در رابط کاربری استفاده می شود.

  • description: متن راهنما (اختیاری)
    • در صورت تعریف، زیر عنوان ستون در جدول پیش‌نمایش نمایش داده می‌شود تا کاربر بداند چه مقداری باید وارد کند.
    • اگر تعریف نشود، فقط label نمایش داده می‌شود.

  • rules / validation: قوانین اعتبارسنجی
    • در هنگام درج گروهی فیلدها براساس این قوانین اعتبارسنجی می‌شوند.
    • validation() نام مستعار (alias) برای rules() است.

  • required: الزامی بودن فیلد
    • با فراخوانی required() قانون required به قوانین اعتبارسنجی اضافه می‌شود (اگر از قبل نباشد).
    • می‌توانید required(false) بزنید تا پرچم را خاموش کنید؛ قوانین از قبل تنظیم‌شده حذف نمی‌شوند.

  • transform: تبدیل مقدار
    • این متد به شما اجازه میدهد تا تغییر دلخواه را بر روی مقداری که از آن فیلد دریافت می شود را اعمال کنید

  • sample: نمونه مقدار
    • در ایجاد فایل نمونه برای فیلد از این مقدار استفاده می شود.
    • مقدار اسکالر → یک سطر نمونه.
    • آرایه از مقادیر → هر عنصر در یک سطر جداگانه در فایل Excel/CSV نوشته می‌شود.

  • type: نوع داده ستون
    • مقدار پیش‌فرض text است.
    • برای تعیین نوع می‌توانید از enum ImportFieldType استفاده کنید (مثل ImportFieldType::LINK).
    • مقدار این فیلد در پردازش قابل خواندن است و می‌توانید براساس آن منطق متفاوت اجرا کنید.

  • defaultValue: مقدار پیش‌فرض فیلد
    • در آرایه نگاشت فیلد ذخیره می‌شود و برای منطق سفارشی در دسترس است.

  • placeholder: متن راهنمای ورودی
    • متن placeholder مرتبط با فیلد در نگاشت ذخیره می‌شود.

  • options: گزینه‌های اضافی
    • آرایه‌ای از متادیتای سفارشی که با array_merge به گزینه‌های قبلی اضافه می‌شود.

  • rulesWhen: قوانین اعتبارسنجی شرطی
    • یک callback دریافت می‌کند که آرایه کامل سطر (شامل مقادیر ستون‌های فایل و inputهای فرم) را به عنوان ورودی می‌گیرد.
    • باید قوانین Laravel validation را برگرداند.
    • در صورت تعریف، برای هر سطر بر rules() اولویت دارد.

  • labelWhen: عنوان شرطی
    • یک callback دریافت می‌کند که آرایه کامل سطر را به عنوان ورودی می‌گیرد.
    • نام فیلد را در پیام‌های خطای اعتبارسنجی و عنوان ستون جدول پیش‌نمایش به‌صورت داینامیک تعیین می‌کند.

  • messagesWhen: پیام خطای سفارشی شرطی
    • یک callback دریافت می‌کند که آرایه کامل سطر را به عنوان ورودی می‌گیرد.
    • کلیدها می‌توانند نام rule (مثل required) یا ترکیب field.rule باشند.

مثال: required، defaultValue، placeholder و options

ImportField::make('isbn')
->column(2)
->label('ISBN')
->required() // معادل rules(['required']) وقتی قوانین خالی باشند
->placeholder('978...')
->defaultValue(null)
->options([
'mask' => '978############',
'help_url' => 'https://example.com/isbn-help',
])
->sample('9786001234567'),

// معادل rules با نام مستعار validation
ImportField::make('title')
->label('عنوان')
->validation(['required', 'string', 'max:255'])
->sample('نمونه عنوان'),

ترتیب ستون‌ها و پیش‌نمایش

ترتیب نمایش در UI از نگاشت فیزیکی فایل جداست.

۱. نگاشت ستون فایل با column() (فقط خواندن/نمونه)

column() فقط مشخص می‌کند داده از کدام ستون فایل خوانده شود و در فایل نمونه کجا نوشته شود — روی ترتیب جدول پیش‌نمایش اثر ندارد.

اولویت هنگام خواندن فایل:

  1. ->column(N) عددی → فقط همان ایندکس (۰‌پایه)
  2. ->column('Header') رشته‌ای → match با نام هدر
  3. بدون column → match با label به‌عنوان نام هدر (و در فایل نمونه، جایگاه به‌ترتیب تعریف در fields())
return [
ImportField::make('title')->column(0)->label('عنوان')->sample('نمونه'),
ImportField::make('author')->column(13)->label('نویسنده')->sample('نمونه'),
ImportField::make('file')->label('فایل')->sample('https://cdn.example.com/cover.jpg'),
];

۲. ترتیب ستون‌ها در جدول پیش‌نمایش

پیش‌نمایش فقط ترتیب آرایه را در نظر می‌گیرد:

  1. فیلدهای fields() به‌ترتیب تعریف
  2. سپس inputهای inputs() به‌ترتیب تعریف

پس برای جابه‌جا کردن ستون‌های UI کافی است ترتیب آیتم‌ها را در fields() / inputs() عوض کنید؛ نیازی به تغییر column() نیست.

ولیدیشن شرطی

گاهی قوانین اعتبارسنجی یک فیلد به مقدار ستون‌های دیگر یا inputهای فرم وابسته است. برای این حالت از متدهای rulesWhen، labelWhen و messagesWhen استفاده کنید.

callbackها آرایه‌ای دریافت می‌کنند که شامل:

  • مقادیر ستون‌های همان سطر از فایل
  • مقادیر inputهای تعریف‌شده در متد inputs() (مثل book_type یا province)

مثال ۱: ولیدیشن بر اساس input فرم

public function inputs(): array
{
return [
ImportInput::make('book_type')
->type('select')
->label('نوع کتاب')
->rules(['required'])
->items([
['id' => 'physical', 'name' => 'فیزیکی'],
['id' => 'digital', 'name' => 'دیجیتال'],
]),
];
}

public function fields(): array
{
return [
ImportField::make('isbn')
->label('ISBN')
->rulesWhen(function (array $row) {
return match ($row['book_type'] ?? null) {
'physical' => ['required', 'string', 'regex:/^978\d{10}$/'],
'digital' => ['nullable', 'string', 'max:50'],
default => ['required', 'string'],
};
})
->labelWhen(function (array $row) {
return match ($row['book_type'] ?? null) {
'physical' => 'شابک (کتاب فیزیکی)',
'digital' => 'شناسه دیجیتال',
default => 'ISBN',
};
})
->messagesWhen(function (array $row) {
if (($row['book_type'] ?? null) !== 'physical') {
return [];
}

return [
'required' => 'برای کتاب فیزیکی، وارد کردن شابک الزامی است.',
'regex' => 'شابک کتاب فیزیکی باید ۱۳ رقم و با 978 شروع شود.',
];
}),

ImportField::make('file')
->label('فایل')
->type(ImportFieldType::LINK)
->rulesWhen(function (array $row) {
return ($row['book_type'] ?? null) === 'digital'
? ['required', 'url']
: ['nullable', 'url'];
})
->labelWhen(function (array $row) {
return ($row['book_type'] ?? null) === 'digital'
? 'لینک فایل دیجیتال'
: 'لینک فایل (اختیاری)';
})
->messagesWhen(function (array $row) {
if (($row['book_type'] ?? null) !== 'digital') {
return [];
}

return [
'required' => 'برای کتاب دیجیتال، لینک فایل الزامی است.',
'url' => 'لینک فایل دیجیتال معتبر نیست.',
];
}),
];
}

مثال ۲: ولیدیشن بر اساس مقدار ستون دیگر

ImportField::make('price')
->label('قیمت')
->rulesWhen(function (array $row) {
if (str_contains(mb_strtolower((string) ($row['title'] ?? '')), 'رایگان')) {
return ['nullable', 'numeric', 'min:0'];
}

return ['required', 'numeric', 'min:1'];
})
->labelWhen(function (array $row) {
return str_contains(mb_strtolower((string) ($row['title'] ?? '')), 'رایگان')
? 'قیمت (کتاب رایگان)'
: 'قیمت';
})
->messagesWhen(function (array $row) {
if (str_contains(mb_strtolower((string) ($row['title'] ?? '')), 'رایگان')) {
return [];
}

return [
'required' => 'برای کتاب غیررایگان، وارد کردن قیمت الزامی است.',
'min' => 'قیمت باید بزرگ‌تر از صفر باشد.',
];
}),
نکته
  • اگر rulesWhen مقدار null برگرداند، از rules() استاتیک به‌عنوان fallback استفاده می‌شود.
  • برای فیلدهای کاملاً شرطی، قوانین را فقط داخل rulesWhen تعریف کنید و از required() استاتیک استفاده نکنید.
  • labelWhen هم روی پیام‌های خطای اعتبارسنجی و هم روی عنوان ستون در جدول پیش‌نمایش اثر می‌گذارد؛ اگر callback مقدار خالی برگرداند، از label() به‌عنوان fallback استفاده می‌شود.

چند سطر نمونه با آرایه در sample()

اگر برای یک فیلد به‌جای مقدار اسکالر، آرایه‌ای به sample() بدهید، در فایل نمونه هر عنصر آرایه در یک سطر جداگانه نوشته می‌شود (نه همه در یک سلول).

ImportField::make('title')
->label('عنوان')
->sample([
'راهنمای معلم آموزش درس9',
'راهنمای معلم آموزش درس10',
'راهنمای معلم آموزش درس11',
]),

ImportField::make('page_number')
->label('شماره صفحه')
->sample([
'10',
'11',
'12',
]),

// مقدار اسکالر فقط در سطر اول نوشته می‌شود؛ سطرهای بعدی خالی می‌مانند
ImportField::make('file')
->label('فایل')
->sample('https://cdn.example.com/cover.jpg'),

قواعد تولید فایل نمونه:

  1. تعداد سطرها = طول بلندترین آرایه sample بین فیلدها.
  2. برای فیلدهایی که آرایه کوتاه‌تری دارند، ایندکس‌های خالی به‌صورت سلول خالی نوشته می‌شوند (می‌توانید صریحاً '' بگذارید).
  3. اگر همه فیلدها اسکالر باشند، فقط یک سطر نمونه ساخته می‌شود.
  4. مقدار اسکالر در کنار آرایه‌ها فقط در سطر اول نوشته می‌شود و در سطرهای بعدی خالی می‌ماند.
توجه

آرایه را به‌صورت PHP array به sample() بدهید؛ رشته‌ای مثل "['a', 'b']" به‌عنوان یک مقدار متنی در یک سلول نوشته می‌شود و به چند سطر تبدیل نمی‌شود.

تشخیص نوع ستون در processRows

اگر در fields() برای یک فیلد نوع تعریف کرده باشید، در زمان پردازش می‌توانید نوع همان ستون را دریافت کنید:

use Dornica\Foundation\Import\Enums\ImportFieldType;

public function processRows(ImportResult $result): void
{
foreach ($result->getValidRows() as $row) {
// به‌صورت enum
if ($this->getFieldType('file') === ImportFieldType::LINK) {
// ...
}

// یا به‌صورت رشته
if ($this->getFieldTypeValue('file') === 'link') {
FileManager::diskDriver('local')
->path('bank/logos')
->module('Bank')
->name('logo_' . time())
->fileType('general')
->uploader(authenticator()->user())
->uploadWithLink($row['file']);
}
}
}

تعریف ورودی‌ها

ورودی‌های تعریف‌شده در متد inputs() در فرم درج گروهی نمایش داده می‌شوند. مقدار انتخاب‌شده در هر input به‌صورت خودکار به همه ردیف‌های فایل اضافه می‌شود؛ یعنی اگر input با نام province_id تعریف کنید، مقدار انتخاب‌شده در هر ردیف در دسترس خواهد بود و می‌توانید در processRows یا rulesWhen از آن استفاده کنید.

در جدول پیش‌نمایش، ستون‌ها به‌ترتیب تعریف در fields() و سپس inputs() نمایش داده می‌شوند؛ column() فقط نگاشت فایل است و ترتیب UI را عوض نمی‌کند.

سلکت ساده (با آیتم‌های ثابت)

public function inputs(): array
{
return [
ImportInput::make('province_id')
->type('select')
->label('استان')
->required()
->rules(['required'])
->items([
['id' => 1, 'name' => 'تهران'],
['id' => 2, 'name' => 'اصفهان'],
['id' => 3, 'name' => 'شیراز'],
])
->containerClass('col-md-6'),
];
}

سلکت وابسته (Depend On)

برای سلکت‌هایی که گزینه‌هایشان بر اساس انتخاب والد از API بارگذاری می‌شود (مثل استان → شهر → منطقه)، از متدهای id()، dependsOn() / parentId() و routeName() استفاده کنید. رفتار مشابه x-select با parentId و routeName در FormComponent است.

مثال کامل: استان → شهر → منطقه

use Dornica\Foundation\Import\Builders\ImportInput;

public function inputs(): array
{
$provinces = Province::query()
->orderBy('name')
->get(['id', 'name'])
->map(fn ($province) => [
'id' => $province->id,
'name' => $province->name,
])
->all();

return [
ImportInput::make('province')
->type('select')
->id('import-province-select')
->label('استان')
->required()
->rules(['required'])
->items($provinces)
->containerClass('col-md-6'),

ImportInput::make('city_id')
->type('select')
->id('import-city-select')
->label('شهر')
->required()
->rules(['required'])
->dependsOn('import-province-select', 'api.admin.api.cities.index')
->displayModel(City::class)
->containerClass('col-md-6'),

ImportInput::make('district_id')
->type('select')
->id('import-district-select')
->label('منطقه')
->dependsOn('import-city-select', 'api.admin.api.districts.index')
->displayModel(District::class)
->containerClass('col-md-6'),
];
}
نکته مهم
  • هر input باید id() یکتا داشته باشد.
  • در dependsOn() باید id سلکت والد را بدهید، نه name فیلد.
  • سلکت والد (مثل استان) معمولاً items دارد؛ سلکت‌های وابسته معمولاً فقط routeName دارند و گزینه‌ها از API لود می‌شوند.

معادل تنظیم دستی (بدون dependsOn)

ImportInput::make('city_id')
->type('select')
->id('import-city-select')
->label('شهر')
->parentId('import-province-select')
->routeName('api.admin.api.cities.index')
->displayModel(City::class),

نمایش نام در پیش‌نمایش (به‌جای ID)

در مرحله «نتایج»، مقادیر inputهای فرم در جدول پیش‌نمایش نمایش داده می‌شوند. به‌صورت پیش‌فرض به‌جای شناسه خام، برچسب (label) گزینه نمایش داده می‌شود. ترتیب resolve به این صورت است:

اولویتروشتوضیح
۱itemsبرای سلکت‌های با لیست ثابت؛ نام از name / title / label خوانده می‌شود
۲displayUsingcallback سفارشی برای تبدیل مقدار به متن نمایشی
۳displayModelخواندن برچسب از مدل Eloquent
۴routeNameاستنتاج خودکار مدل از route (مثلاً cities.index → مدل city)

برای سلکت‌های وابسته (dependsOn) معمولاً items خالی است؛ بنابراین بهتر است صریحاً displayModel() یا displayUsing() تعریف کنید تا در پیش‌نمایش به‌جای ID، نام نمایش داده شود.

مثال ۱: resolve از مدل

ImportInput::make('city_id')
->dependsOn('import-province-select', 'api.admin.api.cities.index')
->displayModel(City::class);

// ستون برچسب و کلید مقدار سفارشی
ImportInput::make('book_id')
->dependsOn('import-grade-select', 'admin.api.v1.books.list')
->displayModel(Book::class, 'title', 'id');

مثال ۲: resolve با callback (مثلاً enum / نوع)

ImportInput::make('type')
->dependsOn('import-book-select', 'admin.api.v1.books.subject-categories-types')
->displayUsing(fn ($value) => match ((int) $value) {
1 => 'فصل',
2 => 'عنوان',
3 => 'هفته',
default => (string) $value,
});

نمایش شناسه خام در پیش‌نمایش

اگر گاهی لازم است در جدول پیش‌نمایش همان ID نمایش داده شود (نه نام)، از showValueInPreview() استفاده کنید:

ImportInput::make('district_id')
->dependsOn('import-city-select', 'api.admin.api.districts.index')
->displayModel(District::class)
->showValueInPreview(); // پیش‌نمایش: id خام
اطلاع

مقادیر ذخیره‌شده در ردیف‌ها همیشه شناسه (ID) هستند؛ متدهای displayModel / displayUsing فقط نمایش پیش‌نمایش را تغییر می‌دهند. در processRows از همان ID استفاده کنید.

پارامترهای ImportInput Builder
متدتوضیح
typeنوع ورودی؛ در حال حاضر فقط select
labelعنوان نمایشی در فرم و پیش‌نمایش
rules / validation / requiredقوانین اعتبارسنجی Laravel؛ validation() نام مستعار rules() است
itemsگزینه‌های سلکت ثابت (id, name)
containerClassکلاس CSS ستون (پیش‌فرض: col-md-6)
placeholderمتن placeholder سلکت
defaultValue / selectedمقدار پیش‌فرض / مقدار ازپیش‌انتخاب‌شده
descriptionتوضیح کمکی
idشناسه HTML سلکت؛ الزامی برای سلکت وابسته
parentIdid سلکت والد
routeNameنام route API برای بارگذاری گزینه‌های وابسته
dependsOn($parentId, $routeName)میانبر parentId + routeName
routeParametersپارامترهای route
parametersپارامترهای اضافی ارسالی به API
allowSelectAllانتخاب همه (سلکت چندتایی)
infiniteScrollفعال‌سازی بارگذاری بی‌نهایت برای گزینه‌های remote
clearableامکان پاک کردن مقدار سلکت (پیش‌فرض: true)
displayModel($class, $displayColumn = 'name', $displayValueColumn = 'id')resolve برچسب پیش‌نمایش از مدل Eloquent
displayColumnستون برچسب هنگام resolve از مدل (پیش‌فرض: name)
displayValueColumnستونی که با مقدار ارسالی سلکت match می‌شود (پیش‌فرض: id)
displayUsingcallback سفارشی برای تبدیل مقدار به برچسب پیش‌نمایش
showValueInPreviewنمایش شناسه خام در پیش‌نمایش به‌جای برچسب

مثال: placeholder، defaultValue، selected، clearable و infiniteScroll

ImportInput::make('province_id')
->type('select')
->label('استان')
->placeholder('یک استان انتخاب کنید')
->defaultValue(1) // اگر selected ست نشده باشد، همین مقدار انتخاب می‌شود
->clearable(true) // امکان پاک کردن انتخاب
->required()
->items([
['id' => 1, 'name' => 'تهران'],
['id' => 2, 'name' => 'اصفهان'],
])
->containerClass('col-md-6'),

ImportInput::make('city_id')
->type('select')
->id('import-city-select')
->label('شهر')
->dependsOn('import-province-select', 'api.admin.api.cities.index')
->infiniteScroll() // بارگذاری تدریجی گزینه‌ها از API
->clearable(false) // جلوگیری از خالی شدن مقدار
->routeParameters(['guard' => 'admin'])
->parameters(['active' => 1])
->displayModel(City::class)
->containerClass('col-md-6'),

// مقدار ازپیش‌انتخاب‌شده صریح
ImportInput::make('book_type')
->type('select')
->label('نوع کتاب')
->selected('physical')
->validation(['required']) // معادل rules(['required'])
->items([
['id' => 'physical', 'name' => 'فیزیکی'],
['id' => 'digital', 'name' => 'دیجیتال'],
]),
نکته
  • selected نسبت به defaultValue در رندر فرم اولویت دارد (selected ?? default_value).
  • اگر کاربر هنوز مقداری انتخاب نکرده باشد، در پردازش از default_value به‌عنوان fallback استفاده می‌شود.

پردازش داده ها

با استفاده از متد processRows، می‌توانید پردازش‌های دلخواه خود را بر روی ردیف‌های داده انجام دهید.

بررسی شرایط قبل از درج (beforeProcessRows)

گاهی قبل از درج داده‌ها باید شرایطی در سیستم برقرار باشد؛ برای مثال وجود «وضعیت شروع» در جدول وضعیت‌ها. در این حالت متد beforeProcessRows را override کنید.

این متد بعد از اعتبارسنجی و انتخاب ردیف‌ها و قبل از processRows اجرا می‌شود. اگر شرط برقرار نباشد، با پرتاب ImportProcessException عملیات متوقف شده و پیام خطا به کاربر نمایش داده می‌شود.

use Dornica\Foundation\Import\Exceptions\ImportProcessException;
use Dornica\Foundation\Import\ImportResult;

public function beforeProcessRows(ImportResult $result): void
{
$startStatus = BookStatus::query()
->where('is_start', true)
->first();

if (!$startStatus) {
throw new ImportProcessException('وضعیت شروع برای کتاب‌ها تعریف نشده است.');
}
}

public function processRows(ImportResult $result): void
{
foreach ($result->getValidRows() as $row) {
Book::create([
'title' => $row['title'],
'author' => $row['author'],
'status_id' => BookStatus::query()->where('is_start', true)->value('id'),
// ...
]);
}
}
نکته
  • beforeProcessRows اختیاری است؛ اگر override نشود، مستقیماً processRows اجرا می‌شود.
  • می‌توانید ImportProcessException را داخل processRows هم پرتاب کنید تا در میانه پردازش، عملیات متوقف و پیام به کاربر برگردانده شود.
  • در مسیر Facade نیز متد apply() ابتدا beforeProcessRows و سپس processRows را فراخوانی می‌کند.

دریافت ردیف‌های معتبر

ابتدا متد getValidRows بر روی شیء ImportResult $result فراخوانی می‌شود. این متد تمام ردیف‌هایی که موفق به عبور از فرآیند اعتبارسنجی شده‌اند را برمی‌گرداند.

دریافت ردیف‌های نامعتبر

برای دسترسی به ردیف‌های حاوی خطا، می‌توانید از متد getFailedRows استفاده کنید که ردیف‌های نامعتبر را برمی‌گرداند.

class BookImport extends BaseImport
{
public function __construct()
{
$this
->setFileFormat(FileFormat::EXCEL)
->setTitle('درج گروهی کتاب ها');
}

public function processRows(ImportResult $result): void
{
$data = [];

foreach ($result->getValidRows() as $row) {
$data[] = [
'title' => $row['title'],
'author' => $row['author'],
'isbn' => $row['isbn'],
'price' => $row['price'],
'published_at' => $row['published_at'],
];
}

Book::insert($data);
}
}
نکته — داده‌های حجیم

اگر تعداد ردیف‌ها زیاد است، پردازش هم‌زمان همهٔ آن‌ها داخل processRows می‌تواند باعث timeout یا مصرف بالای حافظه شود. در این حالت بهتر است به‌جای درج مستقیم در همان متد، داده‌ها را به یک Job (یا چند Job تکه‌تکه) بسپارید و پردازش سنگین را در صف (queue) انجام دهید.


استفاده از Blade Layout Import

در صورتی که از Blade Layout استفاده می‌کنید، می‌توانید فرم وارد کردن داده‌ها را به راحتی در یک نما رندر کنید. این مورد برای پیاده‌سازی سفارشی در پروژه های پنل بسیار مفید است.

ثبت Import در کنترلر
use Dornica\PanelKit\BladeLayout\Facade\BladeLayout;
use App\Imports\BookImport;

public function index()
{
BladeLayout::import(BookImport::class);

return view('books.form');
}
رندر Import در فایل Blade
<x-default-layout>

{!! bladeLayout()->import()->render() !!}

</x-default-layout>

استفاده از Facade Import

کلاس Import برای مدیریت فرآیند وارد کردن داده‌ها از فایل طراحی شده است. این Facade با تکیه بر یک کلاس پیکربندی (Import Class)، عملیات خواندن فایل، اعتبارسنجی و پردازش داده‌ها را به‌صورت مرحله‌ای انجام می‌دهد.

چه زمانی از Facade استفاده کنیم؟

از Facade Import زمانی استفاده کنید که می‌خواهید فرایند import را به‌صورت برنامه‌ای (programmatic) و خارج از UI استاندارد پنل اجرا کنید؛ یعنی خودتان فایل را دریافت کنید، اعتبارسنجی کنید و در صورت نیاز apply را صدا بزنید.

سناریوابزار مناسب
فرم درج گروهی در پنل با پیش‌نمایش و انتخاب ردیفBladeLayout::import(...) (یا دکمه import جدول) — نه Facade
کنترلر / API / Command / Job سفارشیFacade Import
فقط تولید و دانلود فایل نمونه در کد خودتانFacade Import (getSampleFile)
اجرای دستی process سپس apply روی کلاس BaseImportFacade Import
خلاصه
  • برای UI آمادهٔ پنل → BladeLayout / ImportUI
  • برای کنترل کامل در کد خودتان (بدون وابستگی به فرم و پیش‌نمایش پنل) → Facade Import

متدهای اصلی Facade:

متدنقش
config($class)تعیین کلاس پیکربندی (BaseImport)
file($uploadedFile)تعیین فایل آپلودشده
inputs($array)ورودی‌های اضافی فرم (مثل province_id)
process()خواندن فایل و اعتبارسنجی (preProcessRows)
getRows() / getFailedRows()دریافت ردیف‌های معتبر / نامعتبر پس از process
apply()اجرای beforeProcessRows و سپس processRows
getSampleFile()تولید فایل نمونه و بازگرداندن مسیر آن

ایجاد یک نمونه جدید

ابتدا فایل ورودی را دریافت کرده و سپس با استفاده از Facade Import، کلاس پیکربندی و فایل را مشخص می‌کنیم. پس از آن، فرآیند پردازش اجرا می‌شود.

آپلود فایل و پردازش
use Dornica\Foundation\Import\Facades\Import;
use App\Imports\SampleImport;

$uploadedFile = $request->file('file');

$result = Import::config(SampleImport::class)->file($uploadedFile)->process();

$validRows = $result->getRows();
دریافت فایل نمونه
use Dornica\Foundation\Import\Facades\Import;
use App\Imports\SampleImport;

$sampleFilePath = Import::config(SampleImport::class)->getSampleFile();
return response()->download($sampleFilePath);

پردازش

به کمک این متد، فایل مورد نظر خوانده شده و اعتبارسنجی‌های لازم بر اساس تنظیمات تعریف‌شده در پیکربندی اعمال می‌گردد.

دریافت ردیف‌های معتبر

برای دریافت ردیف‌های معتبر پس از پردازش، می‌توان از متد getRows استفاده کرد:

دریافت ردیف‌های نامعتبر

برای دریافت ردیف‌هایی که پردازش آن‌ها ناموفق بوده است، از متد getFailedRows استفاده کنید:

اعمال پردازش

با فراخوانی متد apply، ابتدا beforeProcessRows و سپس پردازش تعریف‌شده در متد processRows بر روی فیلدها طبق پیکربندی اعمال می‌شود.

دریافت نمونه فایل

با فراخوانی متد getSampleFile میتوانید مسیر فایل نمونه را دریافت کنید، این متد یک فایل نمونه تولید می‌کند و مسیر آن را بازمی‌گرداند. اگر column() عددی باشد، هدر/نمونه در همان ستون نوشته می‌شود؛ در غیر این صورت ترتیب تعریف در fields() ملاک است. اگر sample() آرایه باشد، هر عنصر در سطر جداگانه قرار می‌گیرد.


توضیحات تکمیلی

  • config: در این کلاس فیلدها و اطلاعات مربوط به آنها مانند نحوه اعتبار سنجی و ... تعریف می شود

  • file: مشخص کننده فایلی است که داده‌ها از آن وارد می‌شوند. این فایل باید یک نمونه معتبر از UploadedFile باشد.

  • inputs: مجموعه‌ای از ورودی‌های اضافی که ممکن است برای پیکربندی عملیات import لازم باشند.

  • برای کار با فرمت excel باید پکیج phpoffice/phpspreadsheet نصب باشد.

مدیریت خطاها

در صورت عدم وجود کلاس پیکربندی یا خطا در پردازش، کلاس Import یک RuntimeException پرتاب می‌کند که باید به صورت مناسبی مدیریت شود.