Beefeater CRUD Event Bundle is a powerful Symfony bundle for rapid REST API generation with built-in support for CRUD operations, events, pagination, sorting, and filtering.
Create, Read, Update, Delete, List, Patch) based on YAML configurationv1, v2)List operationsThe bundle has been tested with these versions. Newer versions should also work.
Install the bundle via Composer:
composer require beefeater/crud-event-bundle
Add the following to config/routes.yaml:
crud_api_v1:
resource: '%kernel.project_dir%/config/crud_routes_v1.yaml'
type: crud_routes
crud_routes_v1.yamlversion: v1
resources:
products:
entity: App\Entity\Product
operations: [C, R, U, D, L, P]
path: /products
categories:
entity: App\Entity\Category
operations: [C, R, U, D, L, P]
path: /products/{product}/categories
params:
product: App\Entity\Product
products:| Route Name | Method | Path |
|---|---|---|
| api_v1_products_C | POST | /api/v1/products |
| api_v1_products_R | GET | /api/v1/products/{id} |
| api_v1_products_U | PUT | /api/v1/products/{id} |
| api_v1_products_D | DELETE | /api/v1/products/{id} |
| api_v1_products_L | GET | /api/v1/products |
| api_v1_products_P | PATCH | /api/v1/products/{id} |
categories:| Route Name | Method | Path |
|---|---|---|
| api_v1_categories_C | POST | /api/v1/products/{product}/categories |
| api_v1_categories_R | GET | /api/v1/products/{product}/categories/{id} |
| api_v1_categories_U | PUT | /api/v1/products/{product}/categories/{id} |
| api_v1_categories_D | DELETE | /api/v1/products/{product}/categories/{id} |
| api_v1_categories_L | GET | /api/v1/products/{product}/categories |
| api_v1_categories_P | PATCH | /api/v1/products/{product}/categories/{id} |
β οΈ If the
version:key is not specified in the configuration file (e.g.crud_routes_v1.yaml), the route paths will be built without any version prefix, for example:/api/categories/{id}.
All Doctrine repositories must extend the bundleβs AbstractRepository to ensure proper:
Filtering & QuickSearch
Pagination & Sorting
UUID handling
find() throws ResourceNotFoundException
Example
<?php
namespace App\Repository;
use App\Entity\Product;
use Beefeater\CrudEventBundle\Repository\AbstractRepository as BundleAbstractRepository;
use Doctrine\Persistence\ManagerRegistry;
class ProductRepository extends BundleAbstractRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Product::class);
}
}
You can rename the alias if you like; the important part is extending the bundleβs repository.
CrudEventControllerfromJson() accepts validation groups that control which fields are deserializedvalidate() runs validation based on those groups#[Assert\NotBlank(groups: ['create'])]
#[Groups(['create', 'update'])]
private string $name;
This allows flexible validation rules depending on the operation.
You can register event listeners to:
| Parameter | Default |
|---|---|
page |
1 |
pageSize |
25 |
Example:
GET /api/v1/products?page=2&pageSize=10
sort=+field1,-field2 β ascending/descendingGET /api/v1/products?sort=-age,+name
filter[field]=valuefilter[field][operator]=valueSupported operators:
| Operator | Description |
|---|---|
| eq | equals (default) (null safe) |
| like | substring match |
| gte | greater or equal |
| lte | less or equal |
| gt | greater than |
| lt | less than |
| neq | NOT equals (null safe) |
| in | value is in array (null safe) |
| nin | value is NOT in array (null safe) |
Boolean values supported: true, false, none |
Examples:
GET /api/v1/products?filter[isActive]=true
GET /api/v1/products?filter[status][eq]=active
GET /api/v1/products?filter[price][gte]=10&filter[price][lte]=50
GET /api/v1/products?filter[category][in][]=Category1&filter[category][nin][]=Category2
QuickSearch is an optional feature, enabled per resource via configuration and available only for the list operation.
It can be used independently or together with filter and adds LIKE %value% search conditions.
Configuration example:
version: v1
resources:
categories:
entity: App\Entity\Category
operations: [C, R, U, D, L, P]
path: /categories
quick-search: [name, parent.name]
Behaviour
ORfilter using ANDLIKE %value% automaticallyExample
GET /api/v1/categories?filter[isActive]=true&quickSearch=Example
Equivalent query logic:
WHERE
category.is_active = true
AND (
category.name LIKE '%Example%'
OR
parent.name LIKE '%Example%'
)
quickSearch supports both entity properties and related entity properties
(using dot notation: relation.property).If a parent ID (e.g., UUID) is present in the path (e.g., /api/v1/products/{product}/categories), it is:
You can combine all parameters:
GET /api/v1/products/{product}/categories?page=2&pageSize=10&sort=-name,+createdAt&filter[price][gte]=10&filter[price][lte]=50&filter[isAvailable]=true
{resource}.create.on_request{resource}.create.before_persistcrud_event.create.before_persist{resource}.create.after_persistcrud_event.create.after_persist{resource}.update.on_request{resource}.update.before_persistcrud_event.update.before_persist{resource}.update.after_persistcrud_event.update.after_persist{resource}.patch.on_request{resource}.patch.before_persistcrud_event.patch.before_persist{resource}.patch.after_persistcrud_event.patch.after_persist{resource}.delete.on_request{resource}.delete.before_removecrud_event.delete.before_remove{resource}.delete.after_removecrud_event.delete.after_remove{resource}.list.list_settingscrud_event.list.filter_buildentity.before_deserializeApp\EventListener\ProductCrudListener:
tags:
- { name: kernel.event_listener, event: 'products.create.after_persist', method: onAfterPersist }
PayloadValidationException
Thrown when validation fails; includes ConstraintViolationListInterface for detailed violation info.
ResourceNotFoundException
Thrown when the requested resource is not found by ID.
You can register listeners to handle these exceptions globally.
The Beefeater CRUD Event Bundle logs key operations such as:
To enable logging for this bundle, follow these steps:
crud_event in your config/packages/monolog.yaml file. For example, in the dev environment:monolog:
channels:
- crud_event # add this channel alongside your existing ones
when@dev:
monolog:
handlers:
crud_event: # add this handler alongside your existing ones
type: stream
path: "%kernel.logs_dir%/crud_event.log"
level: debug
channels: ["crud_event"]
when@test just change log file path %kernel.logs_dir%/crud_event_test.log.
For the when@prod environment, it's recommended to keep the default logging setup using the fingers_crossed handlerAll logs related to the Beefeater CRUD Event Bundle will be saved in:
var/log/crud_event.log
This setup allows you to conveniently monitor important bundle actions and errors separately from other Symfony logs.
CrudEventController exposes protected handle* methods for reuse in child controllers
You can define your own endpoints, run custom logic (e.g. SECURITY checks), and then call:
β οΈ Note: When using handleList() in your child controllers, make sure to import the types from the bundle:
Security is optional and configured per resource and per operation.
version: v1
resources:
products:
entity: App\Entity\Product
operations: [C, R, U, D, L, P]
path: /products
categories:
entity: App\Entity\Category
operations: [C, R, U, D, L, P]
path: /products/{product}/categories
params:
product: App\Entity\Product
security:
C: [ ROLE_USER ]
U: [ ROLE_USER, ROLE_ADMIN ]
D: [ ROLE_ADMIN ]
Supported operations:
C β CreateR β ReadU β UpdateD β DeleteL β ListP β Patchβ οΈ If the
security:key is not specified in the configuration file (e.g.crud_routes_v1.yaml), security is ignored.β οΈ Security roles must be defined per operation using arrays for
C, R, U, D, L, P.
Missing an operation means public access for that operation.β οΈ Secured endpoints are marked with the
_securedsuffix in the route name, for example:
api_v1_categories_C_securedβ οΈ Multiple roles per operation are supported.
Access is granted if the user has at least one matching role.
The order of user roles does not matter.β οΈ The bundle fully respects Symfony role hierarchy (e.g.
ROLE_ADMINinheritsROLE_USER).β οΈ Security requires the
symfony/security-bundleto be installed.
If it is missing, security will be ignored.
Export is enabled via the export option in resource configuration:
resources:
blog:
entity: App\Entity\Blog
operations: [L]
export: true
If enabled, a list request (L operation) will return an Excel file when the client sends:
Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
How it works
.xlsx)Supported data types
The exporter includes:
Not supported:
If export is not enabled or the Accept header is different, a standard JSON response is returned.
Route parameters can be validated using the requirements option (similar to standard Symfony routing):
blog_list:
path: /blog/{page}/{uuid}/{slug}
controller: App\Controller\BlogController::list
requirements:
page: !php/const Symfony\Component\Routing\Requirement\Requirement::DIGITS # numeric
uuid: !php/const Symfony\Component\Routing\Requirement\Requirement::UUID # UUID
slug: '[a-zA-Z0-9\-]+' # text
requirements must be an arrayHow can I help you explore Laravel packages today?