Installation:
composer require citizen63000/easy-api-bundle
Enable the bundle in config/bundles.php:
Citizen63000\EasyApiBundle\EasyApiBundle::class => ['all' => true],
First Use Case:
Create a CRUD controller for an entity (e.g., Post):
// src/Controller/PostCrudController.php
namespace App\Controller;
use App\Entity\Post;
use App\Form\Type\PostType;
use Citizen63000\EasyApiBundle\Controller\AbstractApiController;
class PostCrudController extends AbstractApiController
{
public const entityClass = Post::class;
public const entityCreateTypeClass = PostType::class;
public const entityUpdateTypeClass = PostType::class;
public const serializationGroups = ['post:read'];
public const listSerializationGroups = ['post:list'];
use CrudControllerTrait;
}
Routing:
Configure routes in config/routes.yaml:
api_post:
resource: "@App\Controller\PostCrudController.php"
type: annotation
Verify:
Run php bin/console debug:router to confirm routes are registered (e.g., /api/post, /api/post/{id}).
CrudControllerTrait and AbstractApiController in the source code.config/packages/easy_api.yaml (auto-generated after installation) for global settings like authentication or inheritance.@Groups in JMS Serializer or Symfony’s #[Groups]).Entity-Centric Development:
entityClass, entityCreateTypeClass, and entityUpdateTypeClass in the controller.FormType) for input validation and transformation.post:read, post:list) to control API output.CRUD Operations: The bundle auto-generates endpoints for:
GET /api/post (list with filters/sorting)GET /api/post/{id} (retrieve)POST /api/post (create)PUT/PATCH /api/post/{id} (update)DELETE /api/post/{id} (delete)Filtering and Sorting:
Configure filterFields and filterSortFields in the controller to enable dynamic queries:
public const filterFields = ['title', 'publishedAt'];
public const filterSortFields = ['title', 'createdAt'];
Clients can then use query params like ?filter[title]=hello&sort=-createdAt.
Authentication:
Enable global auth in config/packages/easy_api.yaml:
easy_api:
authentication: true
user_class: App\Entity\User
The bundle integrates with Symfony’s security system (e.g., JWT, API Token).
Inheritance: Extend base classes for shared behavior:
// src/Controller/AbstractApiController.php
namespace App\Controller;
use Citizen63000\EasyApiBundle\Controller\AbstractApiController as BaseController;
abstract class AbstractApiController extends BaseController
{
// Custom logic (e.g., global serialization groups)
}
Update easy_api.inheritance.controller in config to point to this class.
Custom Actions: Extend the controller to add non-CRUD endpoints:
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class PostCrudController extends AbstractApiController
{
// ... existing CRUD constants ...
#[Route('/api/post/{id}/publish', name: 'api_post_publish', methods: ['POST'])]
public function publish(Post $post): Response
{
$post->setPublished(true);
$this->getEntityManager()->flush();
return $this->json(['status' => 'published']);
}
}
Event Listeners:
Hook into lifecycle events (e.g., prePersist, postUpdate) via Symfony’s event system:
// src/EventListener/PostListener.php
namespace App\EventListener;
use Citizen63000\EasyApiBundle\Event\CrudEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: CrudEvent::PRE_PERSIST, method: 'onPrePersist')]
class PostListener
{
public function onPrePersist(CrudEvent $event): void
{
$entity = $event->getEntity();
$entity->setCreatedAt(new \DateTime());
}
}
Testing:
Use the built-in test helpers (if easy_api.tests.debug: true):
use Citizen63000\EasyApiBundle\Tests\ApiTestTrait;
class PostCrudControllerTest extends WebTestCase
{
use ApiTestTrait;
public function testCreatePost(): void
{
$client = static::createClient();
$response = $this->createEntity($client, Post::class, [
'title' => 'Test Post',
'content' => 'Hello, world!',
]);
$this->assertResponseStatusCodeSame(201);
}
}
Swagger/OpenAPI:
The bundle supports OpenAPI annotations (@OA\*). Extend the base controller to add tags/descriptions:
use OpenApi\Annotations as OA;
#[OA\Tag(name: 'Posts')]
class PostCrudController extends AbstractApiController
{
// ...
}
Serialization Groups:
#[Groups] (Symfony 5.3+) or @Groups (JMS Serializer):
#[Groups(['post:read'])]
private string $title;
php bin/console debug:serializer to verify groups.Form Types:
entityCreateTypeClass/entityUpdateTypeClass must extend AbstractType.AbstractType or the bundle’s AbstractCoreType (if inheritance is configured).Authentication:
easy_api.authentication: true) may break unauthenticated routes.@IsGranted or configure role-based access in the controller:
use Symfony\Component\Security\Http Attribute\IsGranted;
#[IsGranted('ROLE_USER')]
class PostCrudController extends AbstractApiController { ... }
Circular References:
User ↔ Post).max_depth in serialization context or configure serialization.max_depth in config/packages/jms_serializer.yaml.Route Overrides:
/api/post/{id}/publish) may conflict with CRUD routes.Enable Debug Mode:
Set easy_api.tests.debug: true in config/packages/easy_api.yaml to log requests/responses in tests.
Check Events: Dump events in listeners to debug CRUD operations:
#[AsEventListener(event: CrudEvent::PRE_PERSIST)]
public function onPrePersist(CrudEvent $event): void
{
dump($event->getEntity(), $event->getRequest());
}
Validate Configuration:
Run php bin/console debug:config easy_api to verify settings (e.g., user_class, inheritance).
API Test Helper:
Use ApiTestTrait to inspect responses:
$response = $this->getEntity($client, Post::class, 1);
$this->assertJson($response->getContent());
Custom Controllers:
Override AbstractApiController to modify default behavior (e.g., add global middleware):
class CustomApiController extends AbstractApiController
{
public function __construct(private MyCustomService $service)
{
$this->service = $service;
}
}
Dynamic Filtering:
Extend filtering logic by overriding applyFilters() in the controller:
protected function applyFilters(array $
How can I help you explore Laravel packages today?