2lenet/crudit-bundle
Symfony bundle to rapidly build configurable CRUD back offices with SB Admin layout. Provides list views with pagination, sorting, actions, exports and batch ops, plus datasources, filters, menus, workflows, maps, markdown and Twig helpers.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require 2lenet/crudit-bundle
npm install bootstrap@5 sass sass-loader @fortawesome/fontawesome-free easymde --save
Configure Webpack Encore:
{{ encore_entry_link_tags('app') }} to stylesheets block in base.html.twig.assets/styles/app.scss before importing Crudit’s own SCSS:
@import '../../vendor/2lenet/crudit-bundle/assets/sb-admin/css/app.scss';
assets/js/app.js imports ../styles/app.scss.Generate a CRUD:
php bin/console make:crudit
Answer prompts for entity, controller namespace, and filter creation.
Add to Menu:
Register the CRUD in src/Crudit/CrudMenu/AppMenuProvider.php:
LinkElement::new('menu.entity', Path::new('app_crudit_entity_index'))
/entity (auto-generated route).EntityCrudConfig.php (e.g., sorting, fields, actions).Define CRUD Structure:
EntityCrudConfig.php (fields, sorting, actions).EntityController.php (extend for custom logic).EntityType.php (Symfony form types + Crudit helpers).EntityFilterSet.php (query constraints).Field Customization:
Field::new('name', 'text')->setLabel('Full Name');
Field::new('progress', ProgressBarField::class)
->setOptions(['barCssClass' => 'bg-success']);
Field::new('category', EntityType::class, ['class' => Category::class])
->setAutocompleteUrl('app_category_autocomplete');
Actions:
ListAction::new('export', $this->getPath('export'), Icon::new('file-export'))
->setModal('@LleCrudit/modal/_export.html.twig');
ItemAction::new('edit', $this->getPath('edit'), Icon::new('edit'))
->setDisplayIf(fn(Resource $resource) => $resource->isActive());
Filters:
EntityFilterSet and override applyFilters():
public function applyFilters(QueryBuilder $qb, array $filters): void
{
if (isset($filters['active'])) {
$qb->andWhere('e.active = :active')->setParameter('active', $filters['active']);
}
}
Sublists:
getSublists():
Sublist::new('orders', Order::class)
->setTitle('Related Orders')
->setConfig($this->getSublistConfig(Order::class));
Totals:
getTotalFields():
return [
'total' => [
'type' => CrudConfigInterface::SUM,
'field' => Field::new('amount', 'currency'),
],
];
setRole() on actions/fields:
Field::new('adminField')->setRole('ROLE_ADMIN');
templates/LleCrudit/ (e.g., _list.html.twig).npm run dev
SCSS Order:
app.scss to avoid style overrides.@import '../../vendor/2lenet/crudit-bundle/...' to the bottom.Field Label Conflicts:
field. or label. prefixes from IDs in fieldsToUpdate().field.status → use 'status' in fieldsToUpdate().'text.status' → 'text.status').Autocomplete Routes:
DoctrineEntityField fails to autocomplete, ensure the route exists and is annotated:
# config/routes.yaml
app_entity_autocomplete:
path: /entity/autocomplete
controller: App\Controller\EntityAutocompleteController::index
Batch Actions:
->setModal('@LleCrudit/modal/_batch_action.html.twig')
Doctrine Query Builder:
applyFilters() in FilterSet doesn’t break existing queries.$qb->getQuery()->getSQL();
ProgressBarField:
bg-success, bg-danger).min/max values are integers.Menu Registration:
AppMenuProvider.make:crudit again or manually add the LinkElement.Striped Tables:
--crudit-table-striped-bg).app.scss:
.crudit-list {
> tbody > tr:nth-of-type(odd) {
--crudit-table-accent-bg: #f8f9fa;
}
}
Log Queries:
config/packages/dev/doctrine.yaml:
doctrine:
dbal:
logging: true
logging_format: '%%timestamp%% %%sql%%'
Twig Debug:
{{ dump(_context) }}
JavaScript Errors:
easymde for Markdown).rm -rf node_modules && npm install
Custom Form Types:
CruditFormType or use Symfony’s AbstractType.Select2Field:
class Select2Field extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('field', Select2Type::class, $options);
}
}
Override Templates:
vendor/2lenet/crudit-bundle/templates/ to templates/LleCrudit/ and modify.{# templates/LleCrudit/_list_header.html.twig #}
<div class="custom-header">
{{ parent() }}
</div>
Dynamic Actions:
setDisplayIf() for conditional actions:
->setDisplayIf(fn(Resource $resource) => $resource->getStatus() === 'published')
Custom Datasource:
CruditDatasourceInterface for non-Doctrine data (e.g., API, CSV).class ApiDatasource implements CruditDatasourceInterface {
public function getResults(FilterSetInterface $filterSet): array {
return $this->apiClient->fetch($filterSet->getFilters());
}
}
Workflows:
CruditWorkflow to manage multi-step processes (e.g., approvals).$workflow = new CruditWorkflow();
$workflow->addStep('review', 'Review', 'ROLE_REVIEWER');
$workflow->addStep('approve', 'Approve', 'ROLE_ADMIN');
setItemsPerPage() in `CrHow can I help you explore Laravel packages today?