1. Go to this page and download the library: Download tobento/app-import-export library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
tobento / app-import-export example snippets
use Tobento\App\AppFactory;
use Tobento\App\ImportExport\HooksInterface;
use Tobento\App\ImportExport\RegistriesInterface;
use Tobento\App\ImportExport\Queue;
use Tobento\App\ImportExport\Repo;
// Create the app
$app = new AppFactory()->createApp();
// Add directories:
$app->dirs()
->dir(realpath(__DIR__.'/../'), 'root')
->dir(realpath(__DIR__.'/../app/'), 'app')
->dir($app->dir('app').'config', 'config', group: 'config')
->dir($app->dir('root').'public', 'public')
->dir($app->dir('root').'vendor', 'vendor');
// Adding boots
$app->boot(\Tobento\App\ImportExport\Boot\ImportExport::class);
$app->booting();
// Implemented interfaces:
$hooks = $app->get(HooksInterface::class);
$registries = $app->get(RegistriesInterface::class);
$jobRepository = $app->get(Repo\JobRepositoryInterface::class);
$jobResultRepository = $app->get(Repo\JobResultRepositoryInterface::class);
$exportedFileRepository = $app->get(Repo\ExportedFileRepositoryInterface::class);
$queueHandler = $app->get(Queue\QueueHandlerInterface::class);
// Run the app
$app->run();
use Tobento\App\AppFactory;
use Tobento\App\ImportExport\HooksInterface;
use Tobento\App\ImportExport\RegistriesInterface;
use Tobento\App\ImportExport\Queue;
use Tobento\App\ImportExport\Repo;
// Create the app
$app = new AppFactory()->createApp();
// Add directories:
$app->dirs()
->dir(realpath(__DIR__.'/../'), 'root')
->dir(realpath(__DIR__.'/../app/'), 'app')
->dir($app->dir('app').'config', 'config', group: 'config')
->dir($app->dir('root').'public', 'public')
->dir($app->dir('root').'vendor', 'vendor');
// Adding boots
$app->boot(\Tobento\App\ImportExport\Boot\HttpErrorHandler::class);
$app->boot(\Tobento\App\ImportExport\Boot\ImportExport::class);
$app->booting();
// Run the app
$app->run();
'features' => [
new Feature\ImportExport(
// A menu name to show the link or null if none.
menu: 'main',
menuLabel: 'Import & Export Jobs',
// A menu parent name (e.g. 'system') or null if none.
menuParent: null,
// you may disable the ACL while testing for instance,
// otherwise only users with the right permissions can access the page.
withAcl: false,
),
],
'interfaces' => [
Queue\QueueHandlerInterface::class =>
static function(QueueInterface $queue): Queue\QueueHandlerInterface {
return new Queue\TimeBudgetQueueHandler(
queue: $queue,
// You may define a specific queue name. If null, the default queue is used.
queueName: null,
// Maximum processing time per execution (in seconds)
timeBudget: 20,
);
},
],
'features' => [
new Feature\JobResults(
// A menu name to show the link or null if none.
menu: 'main',
menuLabel: 'Job Results',
// A menu parent name (e.g. 'system') or null if none.
menuParent: null,
// you may disable the ACL while testing for instance,
// otherwise only users with the right permissions can access the page.
withAcl: false,
),
],
'features' => [
new Feature\ExportedFiles(
// A menu name to show the link or null if none.
menu: 'main',
menuLabel: 'Exported Files',
// A menu parent name (e.g. 'system') or null if none.
menuParent: null,
// Automatically register the signed media features instance,
// otherwise only users with the right permissions can access the page.
withAcl: false,
),
],
autoRegisterMediaFeatures: false
signedUrlExpiresInMinutes: 5
'features' => [
new Feature\ImportedFiles(
// A menu name to show the link or null if none.
menu: 'main',
menuLabel: 'Imported Files',
// A menu parent name (e.g. 'system') or null if none.
menuParent: null,
// Automatically register the signed media features // When enabled, only users with the correct permissions
// can access the Imported Files page.
withAcl: false,
),
],
autoRegisterMediaFeatures: false
signedUrlExpiresInMinutes: 5
use Psr\Container\ContainerInterface;
use Tobento\App\ImportExport\Registry;
use Tobento\Service\Language\AreaLanguagesInterface;
use Tobento\Service\Language\LanguagesInterface;
class ProductRepositoryWriterRegistry extends Registry\RepositoryWriter
{
protected function resolveLanguages(ContainerInterface $container): LanguagesInterface
{
// Default implementation:
$areaLanguages = $container->get(AreaLanguagesInterface::class);
if ($areaLanguages->has('resources')) {
return $areaLanguages->get('resources');
}
return $container->get(LanguagesInterface::class);
}
}
use Psr\Container\ContainerInterface;
use Tobento\App\ImportExport\Registry;
use Tobento\Service\Language\LanguageFactory;
use Tobento.Service\Language\Languages;
use Tobento\Service\Language\LanguagesInterface;
class ProductRepositoryWriterRegistry extends Registry\RepositoryWriter
{
protected function resolveLanguages(ContainerInterface $container): LanguagesInterface
{
$factory = new LanguageFactory();
return new Languages(
$factory->createLanguage(locale: 'en', default: true),
$factory->createLanguage(locale: 'de', fallback: 'en'),
$factory->createLanguage(locale: 'de-CH', fallback: 'de'),
$factory->createLanguage(locale: 'fr', fallback: 'en', active: false),
$factory->createLanguage(locale: 'it', fallback: 'de', active: false),
);
}
}
'registries' => [
'products.controller.writer' => new Registry\CrudControllerWriter(
// Set a display name:
name: 'Products',
// Set the CRUD controller:
controller: ProductCrudController::class,
// Define the fields that should be writable:
withFields: ['sku', 'status', 'title', 'image', 'created_at'],
// Fields that represent uploaded files and should be processed
// by UploadedFileModifier before writing. See the modifier for details.
withFileFields: ['image'],
),
]
use Psr\Container\ContainerInterface;
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\CrudWriteRepository;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
use Tobento\App\Crud\Field\FieldsInterface;
use Tobento\App\ImportExport\Registry\CrudControllerWriter;
use Tobento\App\ImportExport\Exception\WriterCreateException;
use Tobento\App\ImportExport\Modifier\DotNotationToArrayModifier;
use Tobento\App\ImportExport\Modifier\UploadedFileModifier;
use Tobento\App\ImportExport\Repo\JobEntityInterface;
use Tobento\Service\ReadWrite\Modifier;
use Tobento\Service\ReadWrite\ModifiersInterface;
use Tobento\Service\ReadWrite\RowInterface;
use Tobento\Service\ReadWrite\Writer;
use Tobento\Service\ReadWrite\WriterInterface;
use function Tobento\App\Translation\trans;
class CustomProductWriter extends CrudControllerWriter
{
public function configureFields(ActionInterface $action): iterable|FieldsInterface
{
// Default fields (e.g. dry-run):
yield from parent::configureFields($action);
// Custom option:
yield new Field\Radios(
name: 'writer.skip_validation',
label: trans('Skip validation'),
)
->group(trans('Writer'))
->options(['0' => trans('No'), '1' => trans('Yes')])
->selected(value: '0', action: 'create|edit');
}
public function createWriter(ContainerInterface $container, JobEntityInterface $jobEntity): WriterInterface
{
$writer = parent::createWriter($container, $jobEntity);
// Example: wrap or adjust writer based on custom option:
if ($jobEntity->get('writer.skip_validation') === '1') {
$controller = $container->get($this->controller);
// Use the controller's raw repository without validation (custom behavior)
return new Writer\RepositoryWriter(
repository: $controller->repository(),
idName: $controller->entityIdName(),
columns: $this->withFields,
);
}
return $writer;
}
public function createModifiers(ContainerInterface $container, JobEntityInterface $jobEntity): ModifiersInterface
{
// Start with the default modifiers:
$modifiers = new Modifier\Modifiers(
new Modifier\ColumnMap(map: $jobEntity->columnMap()),
new DotNotationToArrayModifier(),
new UploadedFileModifier($this->withFileFields(), $container),
);
// Add a custom modifier, e.g. trim title:
// Note: Reader applies reader-specific modifiers.
// ColumnMap and writer-specific modifiers are applied here.
$modifiers->add(
new Modifier\Format(
field: 'title',
formatter: function ($value, RowInterface $row): string {
return strtolower(trim((string)$value));
}
)
);
return $modifiers;
}
}
'registries' => [
'csv.file.storage.writer' => new Registry\CsvFileStorageWriter(
// Set a name:
name: 'CSV File',
// File storage where processed files are stored:
storageName: 'uploads-private', // default
// Folder inside the storage where files are placed:
storageFolder: 'exports', // default
),
]
'registries' => [
'csv.file.reader' => new Registry\CsvFileUploadReader(
// Set a name:
name: 'CSV File Upload',
// Limit the maximum upload size (in KB) or null for no limit:
maxFileSizeInKb: null, // default
// File storage where uploaded files are stored:
storageName: 'uploads-private', // default
// Folder inside the storage where files are placed:
storageFolder: 'imports', // default
),
]
'registries' => [
'html.file.storage.writer' => new Registry\HtmlFileStorageWriter(
// Set a display name:
name: 'HTML File',
// Set the allowed file extensions:
allowedFileExtensions: ['html'],
// File storage where processed files are stored:
storageName: 'uploads-private', // default
// Folder inside the storage where files are placed:
storageFolder: 'exports', // default
),
]
'registries' => [
// JSON-only writer
'json.file.storage.writer' => new Registry\JsonFileStorageWriter(
// Set a name:
name: 'JSON File',
// Set the allowed file extensions:
allowedFileExtensions: ['json'],
// File storage where processed files are stored:
storageName: 'uploads-private', // default
// Folder inside the storage where files are placed:
storageFolder: 'exports', // default
),
// NDJSON-only writer
'ndjson.file.storage.writer' => new Registry\JsonFileStorageWriter(
// Set a name:
name: 'NDJSON File',
// Set the allowed file extensions:
allowedFileExtensions: ['ndjson'],
// File storage where processed files are stored:
storageName: 'uploads-private', // default
// Folder inside the storage where files are placed:
storageFolder: 'exports', // default
),
// Combined writer with selectable output format
'json.nd.file.storage.writer' => new Registry\JsonFileStorageWriter(
name: 'JSON or NDJSON File',
allowedFileExtensions: ['json', 'ndjson'],
),
]
'registries' => [
// JSON-only upload reader
'json.file.reader' => new Registry\JsonFileUploadReader(
// Set a name:
name: 'JSON File Upload',
// Set the allowed file extensions:
allowedFileExtensions: ['json'],
// Limit the maximum upload size (in KB) or null for no limit:
maxFileSizeInKb: null, // default
// File storage where uploaded files are stored:
storageName: 'uploads-private', // default
// Folder inside the storage where files are placed:
storageFolder: 'imports', // default
),
// NDJSON-only upload reader
'ndjson.file.reader' => new Registry\JsonFileUploadReader(
// Set a name:
name: 'NDJSON File Upload',
// Set the allowed file extensions:
allowedFileExtensions: ['ndjson'],
// Limit the maximum upload size (in KB) or null for no limit:
maxFileSizeInKb: null, // default
// File storage where uploaded files are stored:
storageName: 'uploads-private', // default
// Folder inside the storage where files are placed:
storageFolder: 'imports', // default
),
// Reader that accepts both JSON and NDJSON uploads
'json.nd.file.reader' => new Registry\JsonFileUploadReader(
name: 'JSON or NDJSON File Upload',
allowedFileExtensions: ['json', 'ndjson'],
),
]
'registries' => [
'products.repository.writer' => new Registry\RepositoryWriter(
// Repository class to write to:
repository: ProductWriteRepository::class,
// Name of the identifier column:
idName: 'id',
// Columns that may be written:
withFields: ['sku', 'title', 'price', 'status'],
// Optional display name:
name: 'Product Writer',
),
]
'registries' => [
'products.storage.reader' => new Registry\StorageReader(
// Storage service ID or class name:
storage: 'storage.mysql',
// Table to read from:
table: 'products',
// Optional query callable:
query: function($query) {
return $query->where('status', '=', 'active');
},
// Optional preview row count:
previewRows: 5,
// Optional display name:
name: 'Active Products Table',
),
]
'registries' => [
'products.storage.writer' => new Registry\StorageWriter(
// Storage service ID or class name:
storage: 'storage.mysql',
// Table to write to:
table: 'products',
// Name of the identifier column:
idName: 'id',
// Columns that may be written:
columns: ['sku', 'title', 'price', 'status'],
// Optional display name:
name: 'Products Storage Writer',
),
]
'registries' => [
'xml.file.storage.writer' => new Registry\XmlFileStorageWriter(
// Set a name:
name: 'XML File',
// File storage where processed files are stored:
storageName: 'uploads-private', // default in AbstractFileStorageWriter
// Folder inside the storage where files are placed:
storageFolder: 'exports', // default in AbstractFileStorageWriter
// Optionally restrict allowed file extensions (defaults to ['xml']):
allowedFileExtensions: ['xml'],
),
]
namespace Tobento\App\ImportExport\Queue;
use Tobento\App\ImportExport\Hook;
use Tobento\Service\Queue\JobHandlerInterface;
class TimeBudgetJobHandler implements JobHandlerInterface
{
protected function mandatoryHooks(): array
{
return [
new Hook\JobLifecycle(name: 'Job Lifecycle'),
];
}
}
use Tobento\App\ImportExport\Hook;
class CustomizedTimeBudgetQueueHandler extends TimeBudgetJobHandler
{
protected function mandatoryHooks(): array
{
return [
new MyCustomJobLifecycleHook(name: 'Custom Job Lifecycle'),
];
}
}
use Tobento\Service\Notifier\Recipient;
'hooks' => [
'notify.dev' => new Hook\Notify(
// Set a name:
name: 'Notify Developer via Mail and SMS',
// Define the recipient:
recipient: new Recipient(
email: '[email protected]',
phone: '15556666666',
channels: ['mail', 'sms'],
),
// Customize the notification subject (optional):
notificationSubject: 'Job :name :event',
// Events to notify on:
notifyOn: ['started', 'partialProcess', 'completed', 'failed'],
// Send the notification via queue (optional):
queueName: 'file', // null by default
// Should this hook be pre-selected in the UI?
defaultSelected: false,
// Optional grouping label used in the UI (default: 'Notify'):
group: 'Notify',
),
]
'hooks' => [
'notify.current-user' => new Hook\NotifyCurrentUser(
// Set a name:
name: 'Keep me updated about this job',
// Define the channels to notify the current user:
channels: ['browser'],
// Customize the notification subject (optional):
notificationSubject: 'Job :name :event',
// Events to notify on (optional):
notifyOn: ['started', 'partialProcess', 'completed', 'failed'],
// Send the notification via queue (optional):
queueName: 'file', // null by default
// Should this hook be pre-selected in the UI?
defaultSelected: true,
// Optional grouping label used in the UI (default: 'Notify'):
group: 'Notify',
),
]
'hooks' => [
'notify.administrators' => new Hook\NotifyUsers(
// Set a name:
name: 'Notify Administrators via Account and Browser',
// Define which user roles should be notified:
roles: ['administrator'],
// Define the channels to notify:
channels: ['storage', 'browser'],
// Customize the notification subject (optional):
notificationSubject: 'Job :name :event',
// Maximum number of users to notify:
limit: 100,
// Send the notification via queue (optional):
queueName: 'file', // null by default
// Events to notify on:
notifyOn: ['started', 'partialProcess', 'completed', 'failed'],
// Should this hook be pre-selected in the UI?
defaultSelected: false,
// Optional grouping label used in the UI (default: 'Notify'):
group: 'Notify',
),
]
'hooks' => [
'save.result.sussessful' => new Hook\SaveJobResult(
// Set a name:
name: 'Save Successful Rows',
// Store rows that were processed successfully:
mode: 'successful',
// Optional grouping label used in the UI (default: 'Row'):
group: 'Row',
),
'save.result.skipped' => new Hook\SaveJobResult(
name: 'Save Skipped Rows',
mode: 'skipped',
),
'save.result.failed' => new Hook\SaveJobResult(
name: 'Save Failed Rows',
mode: 'failed',
),
],
use Tobento\App\ImportExport\Modifier\DotNotationToArrayModifier;
use Tobento\Service\ReadWrite\Modifier;
$modifiers = new Modifier\Modifiers(
new DotNotationToArrayModifier(),
);
use Tobento\App\ImportExport\Modifier\FileExportModifier;
new FileExportModifier(
outputMode: 'url', // 'raw', 'url', or 'base64'
signedUrlsExpiresInMinutes: 60, // for private storage
container: $container,
);
use Tobento\App\ImportExport\Modifier\UploadedFileModifier;
new UploadedFileModifier($this->withFileFields(), $container)
'https://example.com/image.jpg'
'data:image/png;base64,iVBORw0KGgoAAA...'
'iVBORw0KGgoAAAANSUhEUgAA...'
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\ImportExport\Crud\ImportBulkAction;
protected function configureActions(): iterable|ActionsInterface
{
// other actions ...
yield new ImportBulkAction(
// Unique identifier for the bulk action (must be unique per CRUD resource)
name: 'import',
// The label shown in the bulk-action dropdown
title: 'Import',
// Fields available for mapping and writing during the import
withFields: ['id', 'sku', 'title'],
withFileFields: ['image'], // processed by UploadedFileModifier
);
}
use Psr\Container\ContainerInterface;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\ImportExport\Crud\ImportBulkAction;
protected function configureActions(): iterable|ActionsInterface
{
// other actions ...
yield new ImportBulkAction(
name: 'import',
title: 'Import',
// Fields available for mapping and writing during the import
withFields: ['id', 'sku', 'title', 'image'],
// Fields that should be processed by UploadedFileModifier
withFileFields: ['image'],
);
}
public function createModifiers(ContainerInterface $container, JobEntityInterface $jobEntity): ModifiersInterface
{
return new Modifier\Modifiers(
// Maps incoming columns to the fields defined in withFields()
new Modifier\ColumnMap(map: $jobEntity->columnMap()),
// Converts dot-notation keys (e.g. "meta.image") into nested arrays
new DotNotationToArrayModifier(),
// Converts file values (URLs, base64, data URIs) into UploadedFile instances
new UploadedFileModifier($this->withFileFields(), $container),
);
}
use Psr\Container\ContainerInterface;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\ImportExport\Crud\ExportBulkAction;
use Tobento\App\ImportExport\Repo\JobEntityInterface;
use Tobento\Service\ReadWrite\Modifier;
use Tobento\Service\ReadWrite\ModifiersInterface;
use Tobento\Service\ReadWrite\Reader\RepositoryReader;
use Tobento\Service\ReadWrite\RowInterface;
protected function configureActions(): iterable|ActionsInterface
{
// other actions ...
yield new ExportBulkAction(
// Unique identifier for the bulk action (must be unique per CRUD resource)
name: 'export',
// The label shown in the bulk-action dropdown
title: 'Export'
)
// Optional: apply modifiers to transform fields before export
->modifiers(static function (ContainerInterface $container, JobEntityInterface $jobEntity): ModifiersInterface {
// You may conditionally adjust modifiers depending on the writer selected for the job
// $writerId = $jobEntity->writerId(); // e.g. 'pdf.file.storage.writer'
return new Modifier\Modifiers(
// Example: transform the 'sku' field before writing
new Modifier\Format(
field: 'sku',
formatter: function ($value, RowInterface $row) {
return strtoupper(trim((string)$value));
}
),
);
})
// Optional: create custom reader
->reader(static function (ExportBulkAction $action, JobEntityInterface $jobEntity): RepositoryReader {
return new RepositoryReader(
repository: $action->controller()->repository(),
where: ['type' => 'products'], // base filters
objectToArray: static function (object $entity) use ($action): array {
return $action->controller()->createEntityFromObject($entity)->toArray();
},
previewRows: 3,
);
});
}
use Psr\Container\ContainerInterface;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\Fields;
use Tobento\App\Crud\Field\FieldsInterface;
use Tobento\App\ImportExport\Crud\ExportBulkAction;
use Tobento\App\ImportExport\Repo\JobEntityInterface;
use Tobento\Service\ReadWrite\Modifier;
use Tobento\Service\ReadWrite\ModifiersInterface;
use Tobento\Service\ReadWrite\RowInterface;
protected function configureActions(): iterable|ActionsInterface
{
// other actions ...
yield new ExportBulkAction(
name: 'export',
title: 'Export'
)
->modifyFields(function (
ActionInterface $action,
FieldsInterface $fields,
ExportBulkAction $export
): iterable|FieldsInterface {
// Convert to array for modification
$all = $fields->all();
// Add a custom import mode selector
$all[$export->fieldName('options.mode')] = new Field\Select(
name: $export->fieldName('options.mode'),
label: 'Export Mode',
)
->options([
'basic' => 'Basic',
'advanced' => 'Advanced',
'custom' => 'Custom',
]);
// Return new Fields instance
return Fields::fromIterable($all);
})
// Apply modifiers depending on the mode:
->modifiers(static function (ContainerInterface $container, JobEntityInterface $jobEntity): ModifiersInterface {
// Read the selected mode from job options
$mode = $jobEntity->get('options.mode');
// Apply conditional modifiers
if ($mode === 'advanced') {
return new Modifier\Modifiers(
new Modifier\Format(
field: 'sku',
formatter: function ($value, RowInterface $row) {
return strtoupper(trim((string)$value));
}
),
);
}
return new Modifier\Modifiers();
})
}
use Psr\Container\ContainerInterface;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\ImportExport\Crud\ImportBulkAction;
use Tobento\App\ImportExport\Modifier\DotNotationToArrayModifier;
use Tobento\App\ImportExport\Modifier\UploadedFileModifier;
use Tobento\App\ImportExport\Repo\JobEntityInterface;
use Tobento\Service\ReadWrite\Modifier;
use Tobento\Service\ReadWrite\ModifiersInterface;
use Tobento\Service\ReadWrite\RowInterface;
protected function configureActions(): iterable|ActionsInterface
{
// other actions ...
yield new ImportBulkAction(
// Unique identifier for the bulk action (must be unique per CRUD resource)
name: 'import',
// The label shown in the bulk-action dropdown
title: 'Import',
// Fields available for mapping and writing during the import
withFields: ['id', 'sku', 'title'],
// Fields that should be processed by UploadedFileModifier
withFileFields: ['image'],
)
// Optional: apply modifiers to transform fields before import
->modifiers(static function (ContainerInterface $container, JobEntityInterface $jobEntity): ModifiersInterface {
// You may conditionally adjust modifiers depending on the reader selected for the job
// $readerId = $jobEntity->readerId(); // e.g. 'csv.file.reader'
return new Modifier\Modifiers(
// Keep default modifiers
new DotNotationToArrayModifier(),
new Modifier\ColumnMap(map: $jobEntity->columnMap()),
new UploadedFileModifier($jobEntity->withFileFields(), $container),
// Example: transform the 'sku' field before writing
new Modifier\Format(
field: 'sku',
formatter: function ($value, RowInterface $row) {
return strtoupper(trim((string)$value));
}
),
);
});
}
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\Crud\ActionProcessorInterface;
use Tobento\App\Crud\CrudWriteRepository;
use Tobento\App\ImportExport\Crud\ImportBulkAction;
use Tobento\App\ImportExport\Repo\JobEntityInterface;
use Tobento\Service\ReadWrite\Writer\RepositoryWriter;
use Tobento\Service\Repository\NullRepository;
protected function configureActions(): iterable|ActionsInterface
{
// other actions ...
yield new ImportBulkAction(
name: 'import',
title: 'Import',
withFields: ['id', 'sku', 'title'],
)
// This example shows default implementation using CrudWriteRepository::class
->writer(static function (ImportBulkAction $action, JobEntityInterface $jobEntity): RepositoryWriter {
$controller = $action->controller();
$container = $action->container();
$actionProcessor = $container->get(ActionProcessorInterface::class);
// Dry run: replace the repository so no write operations are performed
if ($jobEntity->get('options.dry_run') === '1') {
$controller = $controller->withRepository(new NullRepository());
}
$repository = new CrudWriteRepository(
controller: $controller,
actionProcessor: $actionProcessor,
)->onlyFields(...$action->withFields());
return new RepositoryWriter(
repository: $repository,
idName: $controller->entityIdName(),
columns: $action->withFields(),
);
})
// Example without using CrudWriteRepository::class
->writer(static function (ImportBulkAction $action, JobEntityInterface $jobEntity): RepositoryWriter {
$controller = $action->controller();
return new RepositoryWriter(
repository: $controller->repository(),
idName: $controller->entityIdName(),
columns: $action->withFields(),
);
});
}
use Psr\Container\ContainerInterface;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\Fields;
use Tobento\App\Crud\Field\FieldsInterface;
use Tobento\App\ImportExport\Crud\ImportBulkAction;
use Tobento\App\ImportExport\Repo\JobEntityInterface;
use Tobento\Service\ReadWrite\Modifier;
use Tobento\Service\ReadWrite\ModifiersInterface;
use Tobento\Service\ReadWrite\RowInterface;
protected function configureActions(): iterable|ActionsInterface
{
// other actions ...
yield new ImportBulkAction(
name: 'import',
title: 'Import',
withFields: ['id', 'sku', 'title'],
)
->modifyFields(function (
ActionInterface $action,
FieldsInterface $fields,
ImportBulkAction $import,
int $step,
null|JobEntityInterface $jobEntity
): iterable|FieldsInterface {
// Only modify fields on step 2 (mapping step)
if ($step !== 2) {
return $fields;
}
// Convert to array for modification
$all = $fields->all();
// Add a custom import mode selector
$all[$import->fieldName('options.mode')] = new Field\Select(
name: $import->fieldName('options.mode'),
label: 'Import Mode',
)
->options([
'basic' => 'Basic',
'advanced' => 'Advanced',
'custom' => 'Custom',
]);
// Return new Fields instance
return Fields::fromIterable($all);
})
// Apply modifiers depending on the mode:
->modifiers(static function (ContainerInterface $container, JobEntityInterface $jobEntity): ModifiersInterface {
// Read the selected mode from job options
$mode = $jobEntity->get('options.mode');
// Apply conditional modifiers
if ($mode === 'advanced') {
return new Modifier\Modifiers(
new Modifier\Format(
field: 'sku',
formatter: function ($value, RowInterface $row) {
return strtoupper(trim((string)$value));
}
),
);
}
return new Modifier\Modifiers();
})
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\ImportExport\Crud\ImportBulkAction;
use Tobento\Service\Acl\AclInterface;
class ProductController extends AbstractCrudController
{
public const RESOURCE_NAME = 'products';
/**
* Create a new ProductController.
*
* @param RepositoryInterface $repository
*/
public function __construct(
ProductRepository $repository,
protected AclInterface $acl,
) {
$this->repository = $repository;
}
protected function configureActions(): iterable|ActionsInterface
{
// Other actions...
// Only register the import bulk action if the user is allowed to run imports
if ($this->acl->can('import-export.run')) {
yield new ImportBulkAction(
name: 'import',
title: 'Import',
withFields: ['id', 'sku', 'title'],
);
}
}
}
use Tobento\App\Task\RegistriesInterface;
use Tobento\App\Task\Registry\CommandTask;
$app->on(
RegistriesInterface::class,
static function(RegistriesInterface $registries): void {
$registries->add(id: 'prune.auth.tokens', registry: new CommandTask(
name: 'Prune Auth Tokens',
command: 'auth:purge-tokens',
));
}
);
use Tobento\App\Task\HooksInterface;
use Tobento\App\Task\Hook\Mail;
$app->on(
HooksInterface::class,
static function(HooksInterface $hooks): void {
$hooks->add(id: 'mail.dev', registry: new Mail(
name: 'Mail Developer',
email: '[email protected]',
));
}
);
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.