1. Go to this page and download the library: Download tobento/app-crud 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-crud example snippets
use Tobento\App\AppFactory;
$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').'vendor', 'vendor')
->dir($app->dir('app').'views', 'views', group: 'views')
->dir($app->dir('app').'trans', 'trans', group: 'trans')
->dir($app->dir('root').'build/public', 'public');
// Adding boots:
// you might boot error handlers:
$app->boot(\Tobento\App\Boot\ErrorHandling::class);
$app->boot(\Tobento\App\Crud\Boot\Crud::class);
// you might boot:
$app->boot(\Tobento\App\View\Boot\Breadcrumb::class);
$app->boot(\Tobento\App\View\Boot\Messages::class);
// Run the app:
$app->run();
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Field\FieldsInterface;
use Tobento\App\Crud\Field\FieldInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Action;
use Tobento\App\Crud\Filter\FiltersInterface;
use Tobento\App\Crud\Filter\FilterInterface;
use Tobento\App\Crud\Filter;
use Tobento\Service\Repository\RepositoryInterface;
class ProductsController extends AbstractCrudController
{
/**
* Must be unique, lowercase and only of [a-z-] characters.
*/
public const RESOURCE_NAME = 'products';
/**
* Create a new ProductController.
*
* @param RepositoryInterface $repository
*/
public function __construct(
ProductRepository $repository
) {
$this->repository = $repository;
}
/**
* Returns the configured fields.
*
* @param ActionInterface $action
* @return iterable<FieldInterface>|FieldsInterface
*/
protected function configureFields(ActionInterface $action): iterable|FieldsInterface
{
return [
new Field\PrimaryId('id'),
new Field\Text('sku'),
//...
];
}
/**
* Returns the configured actions.
*
* @return iterable<ActionInterface>|ActionsInterface
*/
protected function configureActions(): iterable|ActionsInterface
{
return [
new Action\Index(title: 'Products'),
//...
];
}
/**
* Returns the configured filters.
*
* @param ActionInterface $action
* @return iterable<FilterInterface>|FiltersInterface
*/
protected function configureFilters(ActionInterface $action): iterable|FiltersInterface
{
return [
new Filter\Columns()->open(false),
//...
];
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Entity\Entity;
use Tobento\App\Crud\Entity\EntityInterface;
use Tobento\Service\Support\Arrayable;
class ProductsController extends AbstractCrudController
{
/**
* Create entity from object.
*
* @param object $object
* @return EntityInterface
*/
public function createEntityFromObject(object $object): EntityInterface
{
// Default mapping:
if ($object instanceof Arrayable) {
return new Entity(
attributes: $object->toArray(),
idAttributeName: $this->entityIdName(),
);
}
if (
method_exists($object, 'toArray')
&& is_array($array = $object->toArray())
) {
return new Entity(
attributes: $array,
idAttributeName: $this->entityIdName(),
);
}
return new Entity(
attributes: (array)$object,
idAttributeName: $this->entityIdName(),
);
}
}
use Tobento\App\Crud\AbstractCrudController;
class ProductsController extends AbstractCrudController
{
/**
* Returns the entity id name.
*
* @return string
*/
protected function entityIdName(): string
{
return 'id';
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Filter\FiltersInterface;
class ProductsController extends AbstractCrudController
{
/**
* Find entities.
*
* @param FiltersInterface $filters
* @return iterable The found entities.
*/
public function findEntities(FiltersInterface $filters): iterable
{
return $this->repository()->findAll(
where: $filters->getWhereParameters(),
orderBy: $filters->getOrderByParameters(),
limit: $filters->getLimitParameter(),
);
}
}
use Tobento\App\Crud\AbstractCrudController;
class ProductsController extends AbstractCrudController
{
/**
* Store entity.
*
* @param array $attributes
* @return object The created entity
*/
public function storeEntity(array $attributes): object
{
return $this->repository()->create(
attributes: $attributes,
);
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Entity\EntityInterface;
class ProductsController extends AbstractCrudController
{
/**
* Update entity.
*
* @param int|string $id
* @param array $attributes
* @param EntityInterface $entity
* @return object The updated entity
*/
public function updateEntity(int|string $id, array $attributes, EntityInterface $entity): object
{
return $this->repository()->updateById(
id: $id,
attributes: $attributes,
);
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Entity\EntityInterface;
class ProductsController extends AbstractCrudController
{
/**
* Delete entity.
*
* @param int|string $id
* @param EntityInterface $entity
* @return void
*/
public function deleteEntity(int|string $id, EntityInterface $entity): void
{
$this->repository()->deleteById(id: $id);
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Action\ActionInterface;
class ProductsController extends AbstractCrudController
{
/**
* Determines if action is processable.
*
* @param ActionInterface $action
* @return void
* @throws \Throwable
*/
public function isActionProcessable(ActionInterface $action): void
{
if ($action->name() === 'edit') {
throw new \Tobento\App\Http\Exception\ForbiddenException();
}
parent::isActionProcessable(action: $action);
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Field\FieldsInterface;
use Tobento\App\Crud\Field\FieldInterface;
use Tobento\App\Crud\Field;
class ProductsController extends AbstractCrudController
{
/**
* Returns the configured fields.
*
* @param ActionInterface $action
* @return iterable<FieldInterface>|FieldsInterface
*/
protected function configureFields(ActionInterface $action): iterable|FieldsInterface
{
return [
new Field\PrimaryId('id'),
new Field\Text(name: 'sku'),
//...
];
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Action;
class ProductsController extends AbstractCrudController
{
/**
* Returns the configured actions.
*
* @return iterable<ActionInterface>|ActionsInterface
*/
protected function configureActions(): iterable|ActionsInterface
{
return [
new Action\Index(title: 'Products'),
new Action\Create(title: 'New product'),
new Action\Store(),
new Action\Edit(title: 'Edit product'),
new Action\Update(),
new Action\Copy(title: 'Copy product'),
new Action\Show(),
new Action\Delete(),
new Action\BulkDelete(),
new Action\BulkEdit(),
];
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Filter\FiltersInterface;
use Tobento\App\Crud\Filter\FilterInterface;
use Tobento\App\Crud\Filter;
class ProductsController extends AbstractCrudController
{
/**
* Returns the configured filters.
*
* @param ActionInterface $action
* @return iterable<FilterInterface>|FiltersInterface
*/
protected function configureFilters(ActionInterface $action): iterable|FiltersInterface
{
return [
new Filter\Columns()->open(false),
new Filter\FieldsSortOrder(),
...new Filter\Fields()
->group('field')
->fields($action->fields())
->toFilters(),
new Filter\PaginationItemsPerPage()->open(false),
// must be added last!
new Filter\Pagination(),
];
}
}
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Filter\FiltersInterface;
use Tobento\App\Crud\Filter;
protected function configureFilters(ActionInterface $action): iterable|FiltersInterface
{
yield new Filter\Columns()->open(false);
if (in_array($action->name(), ['custom'])) {
yield new Filter\FieldsSortOrder();
}
}
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\App\Crud\Action;
use Tobento\App\Crud\Filter;
protected function configureActions(): iterable|ActionsInterface
{
return [
new Action\Index(title: 'Products')
->setFilters(new Filter\Filters(
new Filter\Columns()->open(false),
)),
];
}
use Tobento\App\Boot;
use Tobento\App\Crud\Boot\Crud;
class RoutesBoot extends Boot
{
public const BOOT = [
// you may ensure the crud boot:
Crud::class,
];
public function boot(Crud $crud)
{
$crud->routeController(App\ProductsController::class);
// or:
$crud->routeController(
controller: App\ProductsController::class,
// Register only specific actions:
only: ['index', 'show'],
// Or exclude certain actions:
except: ['bulk'],
// Apply middleware to all generated routes:
middleware: [
SomeMiddleware::class,
],
// Generate localized routes (e.g. /en/products, /de/products):
localized: true,
// Add route parameter constraints for the "id" parameter (default):
whereId: '[a-z0-9]+',
);
}
}
use Tobento\Service\Routing\RouterInterface;
// After adding boots
$app->booting();
$router = $this->app->get(RouterInterface::class);
$name = App\ProductsController::RESOURCE_NAME;
$router->resource($name, App\ProductsController::class);
// needed if you have configured bulk actions:
$router->post($name.'/bulk/{name}', [App\ProductsController::class, 'bulk'])
->name($name.'.bulk');
// Run the app:
$app->run();
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\Action\ActionsInterface;
use Tobento\Service\Acl\AclInterface;
class ProductController extends AbstractCrudController
{
public const RESOURCE_NAME = 'products';
public function __construct(
ProductRepository $repository,
protected AclInterface $acl,
) {
$this->repository = $repository;
}
protected function configureActions(): iterable|ActionsInterface
{
// Read
if ($this->acl->can('products')) {
yield new Action\Index('Products');
yield new Action\Show();
}
// Create
if ($this->acl->can('products.create')) {
yield new Action\Create();
yield new Action\Store();
yield new Action\Copy();
}
// Edit
if ($this->acl->can('products.edit')) {
yield new Action\Edit();
yield new Action\Update();
}
// Delete
if ($this->acl->can('products.delete')) {
yield new Action\Delete();
}
}
}
use Tobento\App\Crud\AbstractCrudController;
use Tobento\App\Crud\ActionProcessorInterface;
use Tobento\App\Crud\CrudWriteRepository;
use Tobento\Service\Repository\WriteRepositoryInterface;
$repository = new CrudWriteRepository(
controller: $controller, // AbstractCrudController
actionProcessor: $actionProcessor, // ActionProcessorInterface
);
var_dump($repository instanceof WriteRepositoryInterface);
// bool(true)
use Tobento\App\Crud\Entity\EntityInterface;
use Tobento\App\Crud\Exception\ValidationException;
use Tobento\Service\Repository\RepositoryCreateException;
try {
$entity = $repository->create([
'title' => 'Lorem ipsum',
]);
var_dump($entity instanceof EntityInterface);
// bool(true)
} catch (RepositoryCreateException $e) {
// do something ...
if ($e->getPrevious() instanceof ValidationException) {
$errorsArray = $e->getPrevious()->validation()->errors()->toArray();
}
}
use Tobento\App\Crud\Field;
new Field\Buttons(name: 'btns')
->buttons(
new Button\Button(label: 'Save', group: 'entity')
->name('save')
->attr(name: 'name', value: 'next_action')
->attr(name: 'value', value: 'edit')
->attr(name: 'data-loading', value: 'true')
->ajaxAction('Record saved successfully.')
->primary(),
new Button\Button(label: 'Save & Close', group: 'entity')
->name('close')
->attr(name: 'data-loading', value: 'true')
->ajaxAction(),
)
// Alignment options:
->alignLeft() // default if none is set.
->alignRight()
->alignCenter()
// Render buttons as a field layout:
->displayAsField();
use Tobento\App\Crud\Field;
new Field\Checkboxes(
name: 'colors',
// you may set a label, otherwise name is used:
label: 'Colors',
);
new Field\Checkboxes('colors')
// specify the options using an array:
->options(['blue' => 'Blue', 'red' => 'Red'])
// or using a closure (parameters are resolved by autowiring):
->options(fn(ColorsRepository $repo): array => $repo->findColors());
new Field\Checkboxes('colors')
->emptyOption(value: '_none');
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
new Field\Checkboxes('colors')
->selected(value: ['blue', 'red'], action: 'create')
// or using a closure (additional parameters are resolved by autowiring):
->selected(
value: function (ActionInterface $action, FieldInterface $field): null|array {
return ['blue', 'red'];
//return null; // if none is selected
},
action: 'edit',
)
use Tobento\App\Crud\Field;
new Field\Checkboxes('colors')->attributes(['class' => 'name']);
new Field\Checkboxes('colors')
->validate('
use Tobento\App\Crud\Field;
new Field\File(
name: 'file',
// you may set a label, otherwise name is used:
label: 'File',
);
use Tobento\App\Crud\Field;
new Field\File(name: 'file')
->fields(
new Field\Text('alt', 'Alternative Text')->translatable(),
new Field\Radios('buyable', 'Buyable')->displayInline()->options(['no', 'yes']),
);
use Tobento\App\Crud\Field;
new Field\File(name: 'file')
->translatable();
use Tobento\App\Crud\Field;
new Field\File(name: 'file')
->fileSource(function(Field\FileSource $fs): void {
$fs->storage('uploads-public')
->allowedExtensions('jpg', 'png')
->imageEditor(template: 'default');
});
use Tobento\App\Crud\Field;
new Field\File(name: 'file')
->fields(
new Field\Text('name', 'Filename')->translatable(),
)
->storeFilenameTo(field: 'name');
// with using a filename modifier (callable):
->storeFilenameTo(field: 'name', modify: static function(string $filename, null|string $locale): string {
return $filename;
});
use Tobento\App\Crud\Field;
new Field\Files(
name: 'files',
// you may set a label, otherwise name is used:
label: 'Files',
);
use Tobento\App\Crud\Field;
new Field\Files(name: 'files')
->fields(
new Field\Text('alt', 'Alternative Text')->translatable(),
new Field\Radios('buyable', 'Buyable')->displayInline()->options(['no', 'yes']),
);
use Tobento\App\Crud\Field;
new Field\Files(name: 'files')
->file(function(Field\File $file): void {
$file->translatable();
$file->fileSource(function(Field\FileSource $fs): void {
$fs->allowedExtensions('png');
});
});
use Tobento\App\Crud\Field;
new Field\Files(name: 'files')
->translatable()
->file(function(Field\File $file): void {
$file->translatable();
$file->fileSource(function(Field\FileSource $fs): void {
$fs->allowedExtensions('png');
});
});
use Tobento\App\Crud\Field;
new Field\Files(name: 'files')
// min only;
->numberOfFiles(min: 1)
// or max only:
->numberOfFiles(max: 10)
// or min and max:
->numberOfFiles(min: 1, max: 10);
use Tobento\App\Crud\Field;
new Field\FileSource(
name: 'file',
// you may set a label, otherwise name is used:
label: 'File',
);
new Field\FileSource('image')
->storage(name: 'custom-uploads');
new Field\FileSource('image')
->storage(name: 'uploads-private')
->allowPublicPicturePreview();
new Field\FileSource('image')
->folder(path: 'shop/products')
// or using a callable:
->folder(static function(): string {
return sprintf('product/%s/%s/', date('Y'), date('m'));
});
new Field\FileSource('image')
->allowedExtensions('jpg', 'png')
// you may set max file size in KB:
->maxFileSizeInKb(1000) // or null unlimited (default)
// you may set the file as
use Tobento\Service\Upload\Validator;
use Tobento\Service\Upload\ValidatorInterface;
new Field\FileSource('image')
->validator(static function(): ValidatorInterface {
return new Validator\General(
allowedExtensions: ['jpg'],
strictFilenameCharacters: true,
maxFilenameLength: 200,
maxFileSizeInKb: 2000,
);
});
use Tobento\App\Media\Upload\ImageProcessor;
use Tobento\App\Media\Upload\Writer\SvgSanitizer;
use Tobento\Service\FileStorage\StorageInterface;
use Tobento\Service\Upload\CopyFileWrapper;
use Tobento\Service\Upload\FileStorageWriter;
use Tobento\Service\Upload\FileStorageWriterInterface;
use Tobento\Service\Upload\Writer;
new Field\FileSource('image')
->fileStorageWriter(static function(StorageInterface $storage, mixed $inputFile): FileStorageWriterInterface {
$writers = [];
// Only process images for real uploads
if (! $inputFile instanceof CopyFileWrapper) {
$writers[] = new Writer\Image(
imageProcessor: new ImageProcessor(
actions: [
'orientate' => [],
'resize' => ['width' => 2000],
],
),
);
$writers[] = new SvgSanitizer();
}
return new FileStorageWriter(
storage: $storage,
filenames: FileStorageWriter::ALNUM, // RENAME, ALNUM, KEEP
duplicates: FileStorageWriter::RENAME, // RENAME, OVERWRITE, DENY
folders: FileStorageWriter::ALNUM, // or KEEP
folderDepthLimit: 5,
writers: $writers,
);
});
use Tobento\Service\Upload\UploadedFileFactoryInterface;
new Field\FileSource('image')
->modifyInputValue(
modifier: function(
mixed $value,
Field\FileSource $field,
UploadedFileFactoryInterface $uploadedFileFactory
): mixed {
// Convert a remote URL into an UploadedFile instance
if (is_string($value)) {
return $uploadedFileFactory->createFromRemoteUrl($value);
}
return $value;
},
action: 'store|update', // default
);
use Tobento\Service\Picture\DefinitionInterface;
use Tobento\Service\Picture\Definition\ArrayDefinition;
new Field\FileSource('image')
->picture(definition: [
'img' => [
'src' => [120],
'loading' => 'lazy',
],
'sources' => [
[
'srcset' => [
'' => [120],
],
'type' => 'image/webp',
],
],
])
// or you may use a definition object implementing the DefinitionInterface:
->picture(definition: new ArrayDefinition('product-image', [
'img' => [
'src' => [120],
'loading' => 'lazy',
],
'sources' => [
[
'srcset' => [
'' => [120],
],
'type' => 'image/webp',
],
],
]))
// or you may disable it, showing just the path:
->picture(definition: null)
// You may queue the picture generation:
->pictureQueue(true);
new Field\FileSource('image')
->imageEditor(template: 'crud');
'listeners' => [
\Tobento\App\Media\Event\ImageEdited::class => [
[\Tobento\App\Crud\Listener\ClearGeneratedPicture::class, ['definition' => 'crud-file-src']],
// you add more when using different definitions:
[\Tobento\App\Crud\Listener\ClearGeneratedPicture::class, ['definition' => 'product-image']],
],
],
new Field\FileSource('image')
->pictureEditor(template: 'default', definitions: ['product-main', 'product-list']);
new Field\FileSource('image')
->displayMessages('error', 'success', 'info', 'notice');
use Tobento\App\Crud\Field;
new Field\Group(name: 'seo')
// define the fields:
->fields(
new Field\Text('meta_title', 'Meta Title')->translatable(),
new Field\Text('meta_desc', 'Meta Description')->translatable(),
)
// you may group the fields, otherwise groups from the defined fields are used:
->group('SEO')
// you may prepend the group name to each field:
->prependGroupName()
// you may display the fields as field layout:
->displayAsField()
// you may display the fields as card layout:
->displayAsCard()
// you may display the group label when displaying as card:
->displayLabel();
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
class SeoFields extends Field\Group
{
protected function configure(): void
{
$this->group('Seo');
$this->fields();
}
public function fields(FieldInterface ...$fields): static
{
$this->fields = [
new Field\Text('meta_title', 'Meta Title')->translatable(),
new Field\Text('meta_desc', 'Meta Description')->translatable(),
];
return $this;
}
}
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
use Tobento\App\Crud\Field\FieldsInterface;
new SeoFields(name: 'seo')
// you may rename fields:
->renameFields(['meta_desc' => 'meta_description'])
// you may remove fields:
->removeField('meta_title')
// you may modify fields:
->modifyField(name: 'meta_title', modifier: function(FieldInterface $field): void {
$field->translatable(false);
})
// you may modify fields:
->modifyFields(modifier: function(FieldsInterface $fields): FieldsInterface {
// modify ...
return $fields;
});
use Tobento\App\Crud\Field;
new Field\Html(
name: 'title',
);
use Tobento\App\Crud\Field;
new Field\Html(name: 'title')->content(html: '<p>Lorem</p>');
use Tobento\App\Crud\Field;
use Tobento\Service\View\ViewInterface;
new Field\Html(name: 'title')->content(function (Field\Html $field, ViewInterface $view): string {
return $view->render('about', []);
});
use Tobento\App\Crud\Field;
new Field\Html(name: 'title')
->content(html: '<p>Lorem</p>')
->indexable(true) // default false
->showable(true); // default false
use Tobento\App\Crud\Field;
new Field\Items('prices')
// you may group the fields
->group('Prices') // set before defining fields!
// define the fields per item:
->fields(
new Field\Text('price_net', 'Price Net')
->type('number')
->attributes(['step' => 'any'])
->validate('decimal'),
new Field\Text('price_gross', 'Price Gross')
->type('number')
->attributes(['step' => 'any'])
->validate('decimal'),
)
// you may display items as card layout:
->displayAsCard()
// you may restrict items:
->validate('
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
new Field\Items('items')
->onCreateField(
callback: function(
FieldInterface $template,
int $index,
array $rowInput
): null|FieldInterface {
// Example: add info text based on the row index
$template->infoText('Item #'.$index);
// Example: conditionally replace the field
if (($rowInput['type'] ?? null) === 'special') {
return new Field\Text(name: 'special');
}
// Return the modified template field
return $template;
}
);
use Tobento\App\Crud\Field;
new Field\Options(
name: 'categories',
// you may set a label, otherwise name is used:
label: 'Categories',
);
use Tobento\Service\Repository\RepositoryInterface;
new Field\Options('categories')
->repository(CategoriesRepository::class) // class-string|RepositoryInterface
// you may add base where queries:
->baseWhere(['type' => 'blog'])
// you may change the limit of the searchable options to be displayed:
->limit(15) // default is 25
// you may change the column value to be stored:
->storeColumn('sku') // 'id' is default
// you may change the search columns:
->searchColumns('title', 'sku'); // 'title' is default
use Tobento\App\Crud\Field;
use Tobento\Service\View\ViewInterface;
new Field\Options('categories')
->toOption(function(object $item, ViewInterface $view, Field\Options $options): Field\Option {
return new Field\Option(
value: (string)$item->get('id'),
text: (string)$item->get('title'),
);
});
use Tobento\App\Crud\Field;
use Tobento\Service\View\ViewInterface;
new Field\Options('categories')
->toOption(function(object $item, ViewInterface $view, Field\Options $options): Field\Option {
return new Field\Option(value: (string)$item->get('id'))
->text((string)$item->get('title'))
->text((string)$item->get('sku'))
->html('html') // must be escaped!
->image(
image: $item->get('image', []),
view: $view,
);
});
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
new Field\Options('categories')
->selected(value: ['2', '5'], action: 'create')
// or using a closure (additional parameters are resolved by autowiring):
->selected(
value: function (ActionInterface $action, FieldInterface $field): null|array {
return ['2', '5'];
//return null; // if none is selected
},
action: 'edit',
)
new Field\Options('categories')
->placeholder(text: 'Search categories');
new Field\Options('categories')
->emptyOption(value: '_none');
new Field\Options('categories')
->validate('
use Tobento\App\Crud\Field;
new Field\PrimaryId(name: 'id', label: 'ID');
use Tobento\App\Crud\Field;
new Field\Radios(
name: 'colors',
// you may set a label, otherwise name is used:
label: 'Colors',
);
new Field\Radios('colors')
// specify the options using an array:
->options(['blue' => 'Blue', 'red' => 'Red'])
// or using a closure (parameters are resolved by autowiring):
->options(fn(ColorsRepository $repo): array => $repo->findColors());
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
new Field\Radios('colors')
->selected(value: 'blue', action: 'create')
// or using a closure (additional parameters are resolved by autowiring):
->selected(
value: function (ActionInterface $action, FieldInterface $field): null|string {
return 'blue';
//return null; // if none is selected
},
action: 'edit',
)
use Tobento\App\Crud\Field;
new Field\Radios('colors')->attributes(['class' => 'name']);
new Field\Radios('colors')
->displayInline();
new Field\Radios('colors')
->validate('
use Tobento\App\Crud\Field;
new Field\Select(
name: 'colors',
// you may set a label, otherwise name is used:
label: 'Colors',
);
new Field\Select('colors')
// specify the options using an array:
->options(['blue' => 'Blue', 'red' => 'Red'])
// or using a closure (parameters are resolved by autowiring):
->options(fn(ColorsRepository $repo): array => $repo->findColors());
new Field\Select('colors')
// specify the options using an array:
->options(['blue' => 'Blue', 'red' => 'Red'])
->emptyOption(value: 'none', label: '---');
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
new Field\Select('colors')
->selected(value: 'value', action: 'create')
// or using a closure (additional parameters are resolved by autowiring):
->selected(
value: function (ActionInterface $action, FieldInterface $field): null|string|array {
return ['blue', 'red']; // if multiple selection
//return 'blue'; // if single selection
//return null; // if none is selected
},
action: 'edit',
)
use Tobento\App\Crud\Field;
new Field\Select('colors')->attributes(['multiple', 'size' => '10']);
new Field\Select('colors')->optionAttributes([
// all options using wildcard:
'*' => ['data-foo' => 'value'],
// specific option using option value:
'blue' => ['data-bar' => 'value'],
]);
new Field\Select('colors')->optgroupAttributes(['data-foo' => 'value']);
new Field\Select('colors')
->validate('ection:
new Field\Select('colors')
->attributes(['multiple', 'size' => '10'])
->validate('
use Tobento\App\Crud\Field;
new Field\SingleOptions(
name: 'category',
// you may set a label, otherwise name is used:
label: 'Category',
);
use Tobento\Service\Repository\RepositoryInterface;
new Field\SingleOptions('category')
->repository(CategoriesRepository::class) // class-string|RepositoryInterface
// you may add base where queries:
->baseWhere(['type' => 'blog'])
// you may change the limit of the searchable options to be displayed:
->limit(15) // default is 25
// you may change the column value to be stored:
->storeColumn('sku') // 'id' is default
// you may change the search columns:
->searchColumns('title', 'sku'); // 'title' is default
use Tobento\App\Crud\Field;
use Tobento\Service\View\ViewInterface;
new Field\SingleOptions('category')
->toOption(function(object $item, ViewInterface $view, Field\SingleOptions $options): Field\Option {
return new Field\Option(
value: (string)$item->get('id'),
text: (string)$item->get('title'),
);
});
use Tobento\App\Crud\Field;
use Tobento\Service\View\ViewInterface;
new Field\SingleOptions('category')
->toOption(function(object $item, ViewInterface $view, Field\SingleOptions $options): Field\Option {
return new Field\Option(value: (string)$item->get('id'))
->text((string)$item->get('title'))
->text((string)$item->get('sku'))
->html('html') // must be escaped!
->image(
image: $item->get('image', []),
view: $view,
);
});
use Tobento\App\Crud\Action\ActionInterface;
use Tobento\App\Crud\Field;
use Tobento\App\Crud\Field\FieldInterface;
new Field\SingleOptions('category')
->selected(value: '2', action: 'create')
// or using a closure (additional parameters are resolved by autowiring):
->selected(
value: function (ActionInterface $action, FieldInterface $field): null|string {
return '2';
//return null; // if none is selected
},
action: 'edit',
)
new Field\SingleOptions('category')
->placeholder(text: 'Search categories');
new Field\SingleOptions('categories')
->displayAsModal();
new Field\SingleOptions('category')
->validate('
use Tobento\App\Crud\Field;
new Field\Slug(
name: 'slug',
// you may set a label, otherwise name is used:
label: 'SLUG',
);
use Tobento\App\Crud\Field;
new Field\Slug('slug')->fromField('title');
use Tobento\App\Crud\Field;
use Tobento\Service\Slugifier\SlugifierInterface;
use Tobento\Service\Slugifier\SlugifiersInterface;
// using slugifier name:
new Field\Slug('slug')->slugifier('crud');
// 'crud' is set as default but fallsback to default as not defined in slugging config
// using object:
new Field\Slug('slug')->slugifier(new Slugifier());
// using closure:
new Field\Slug('slug')
->slugifier(function (SlugifiersInterface $slugifiers): SlugifierInterface {
return $slugifiers->get('custom');
});
use Tobento\App\Crud\Field;
new Field\Slug('slug')->uniqueSlugs(false);
use Tobento\App\Crud\Field;
new Field\Slug(name: 'title')->attributes(['data-foo' => 'value']);
use Tobento\App\Crud\Field;
new Field\Slug('slug')
->fromField('title')
->translatable()
->readonly(true, action: 'edit|update');
use Tobento\App\Crud\Field;
new Field\Text(
name: 'title',
// you may set a label, otherwise name is used:
label: 'TITLE',
);
use Tobento\App\Crud\Field;
new Field\Text(name: 'email')->type('email');
file
app/config/file_storage.php
app/config/event.php
app/config/event.php
app/config/slugging.php
app/config/slugging.php
app/config/slugging.php
php
use Tobento\Service\Repository\Storage\Column\Text;
use Tobento\Service\Repository\Storage\Column\ColumnsInterface;
use Tobento\Service\Repository\Storage\StorageRepository;
use function Tobento\App\HtmlSanitizer\sanitizeHtml;
class ExampleRepository extends StorageRepository
{
protected function configureColumns(): iterable|ColumnsInterface
{
return [
// ...
new Column\Text('desc', type: 'text')
// as there might be data stored before, we clean the html on reading:
->read(fn (string $value): string => sanitizeHtml($value))
// clean the html on writing:
->write(fn (string $value): string => sanitizeHtml($value))
// ...
];
}
}
php
<?= $view->sanitizeHtml(html: $html)
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.