Installation
composer require desksheet/rest-bundle
Add to config/bundles.php:
return [
// ...
Desksheet\RestBundle\DesksheetRestBundle::class => ['all' => true],
];
First Use Case: Basic CRUD Endpoint
src/Entity/Book.php):
namespace App\Entity;
use Desksheet\RestBundle\Annotation\Rest;
#[Rest]
class Book {}
php bin/console desksheet:rest:generate
/api/books (GET, POST, PUT, DELETE).Key Files to Review
config/packages/desksheet_rest.yaml (default config).src/Entity/ (annotated entities for auto-routing).src/Controller/ (custom logic overrides).@Rest to auto-generate RESTful routes (e.g., GET /api/{entity}).
#[Rest(
routePrefix: 'api/v1',
collectionOperations: ['get', 'post'],
itemOperations: ['get', 'put', 'delete']
)]
class Product {}
config/packages/desksheet_rest.yaml:
desksheet_rest:
resources:
App\Entity\Product:
collection:
- { method: 'GET', path: '/products' }
- { method: 'POST', path: '/products', name: 'create_product' }
item:
- { method: 'GET', path: '/products/{id}' }
#[Groups] for field filtering:
use Symfony\Component\Serializer\Annotation\Groups;
class User {
#[Groups(['user:read'])]
private string $name;
#[Groups(['user:write'])]
private string $email;
}
config/packages/desksheet_rest.yaml:
desksheet_rest:
serialization:
groups: ['user:read', 'user:write']
desksheet.rest.pre_read/post_write events for pre/post-processing:
// src/EventListener/CustomListener.php
namespace App\EventListener;
use Desksheet\RestBundle\Event\RestEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CustomListener implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
RestEvent::PRE_READ => 'onPreRead',
RestEvent::POST_WRITE => 'onPostWrite',
];
}
public function onPreRead(RestEvent $event) {
$event->getData()->set('custom_field', 'value');
}
}
use Symfony\Component\Validator\Constraints as Assert;
class Order {
#[Assert\NotBlank]
#[Assert\Positive]
private int $quantity;
}
POST/PUT via bundle config:
desksheet_rest:
validation: true
desksheet_rest:
pagination:
enabled: true
items_per_page: 20
Route Conflicts
routePrefix in annotations or desksheet_rest.yaml to namespace routes.php bin/console debug:router | grep desksheet
Serialization Mismatches
ObjectNormalizer.php bin/console debug:serializer App\Entity\Book
Event Dispatching Order
PRE_READ fires before data is fetched; POST_READ fires after. Modify data in PRE_READ to affect responses.PRE_READ.Caching Headers
ETag/Last-Modified. Add manually in listeners or controllers:
$event->getResponse()->setETag(md5($data));
# config/packages/dev/desksheet_rest.yaml
desksheet_rest:
debug: true
php bin/console debug:router | grep desksheet
php bin/console debug:validator App\Entity\Book
Custom Controllers
@RestController:
#[RestController]
class CustomBookController extends AbstractRestController {
public function getCollection(Options $options) {
// Custom logic
}
}
Dynamic Route Parameters
#[Rest(routeName: 'custom_route')] to bind to existing routes or custom logic.API Platform Integration
Testing
Desksheet\RestBundle\Test\RestTestCase for functional tests:
use Desksheet\RestBundle\Test\RestTestCase;
class BookTest extends RestTestCase {
public function testGetBooks() {
$this->client->request('GET', '/api/books');
$this->assertResponseIsSuccessful();
}
}
Hydrator by default. For complex queries, add #[ORM\QueryBuilder] to entities or use DQL in PRE_READ events.#[ORM\QueryBuilder] to limit results:
#[ORM\QueryBuilder(
class: Book::class,
method: 'createPaginatedQueryBuilder',
options: ['limit' => 50]
)]
class Book {}
How can I help you explore Laravel packages today?