redhotmagma/apibundle
Laravel package that bundles common API building blocks—helpers, responses, request validation, and boilerplate to speed up creating consistent JSON endpoints. Designed to reduce repetitive setup and keep API structure standardized across projects.
Installation
composer require redhotmagma/apibundle
Add the bundle to config/bundles.php (Symfony) or config/app.php (Laravel via Symfony bridge):
RedHotMagma\ApiBundle\RedHotMagmaApiBundle::class => ['all' => true],
First Use Case: Basic Entity Transformation Define a simple DTO (Data Transfer Object) and map it to a Doctrine entity:
// src/Dto/UserDto.php
namespace App\Dto;
use RedHotMagma\ApiBundle\Dto\AbstractDto;
class UserDto extends AbstractDto
{
public ?int $id;
public string $name;
public string $email;
}
Create a transformer to handle conversion:
// src/Transformer/UserTransformer.php
namespace App\Transformer;
use App\Dto\UserDto;
use App\Entity\User;
use RedHotMagma\ApiBundle\Transformer\AbstractTransformer;
class UserTransformer extends AbstractTransformer
{
public function transformToEntity(array $data): User
{
$user = new User();
$user->setName($data['name']);
$user->setEmail($data['name']);
return $user;
}
public function transformToArray(User $entity): array
{
return [
'id' => $entity->getId(),
'name' => $entity->getName(),
'email' => $entity->getEmail(),
];
}
}
Register Transformer
Bind the transformer in a service provider (e.g., AppServiceProvider):
public function register()
{
$this->app->bind(UserTransformer::class);
}
First API Endpoint Use the transformer in a controller:
// src/Controller/UserController.php
use App\Dto\UserDto;
use App\Transformer\UserTransformer;
use RedHotMagma\ApiBundle\Dto\DtoManager;
class UserController extends AbstractController
{
public function __construct(
private DtoManager $dtoManager,
private UserTransformer $transformer
) {}
public function create(UserDto $dto): JsonResponse
{
$entity = $this->transformer->transformToEntity($dto->toArray());
// Save entity logic here...
return $this->json($this->transformer->transformToArray($entity));
}
}
DTO-Driven API Development
use Symfony\Component\Validator\Constraints as Assert;
class UserDto extends AbstractDto
{
#[Assert\NotBlank]
public string $name;
#[Assert\Email]
public string $email;
}
DtoManager to bind DTOs to request data:
public function update(Request $request, UserDto $dto)
{
// $dto is automatically populated from $request
}
Transformer Layer
AbstractTransformer for nested entities:
class PostTransformer extends AbstractTransformer
{
public function transformToArray(Post $post): array
{
return [
'id' => $post->getId(),
'title' => $post->getTitle(),
'author' => $this->getTransformer(UserTransformer::class)
->transformToArray($post->getAuthor()),
];
}
}
Dependency Injection
$this->app->bind(UserTransformer::class);
$this->app->bind(UserDto::class);
DtoManager to dynamically resolve DTOs:
$dto = $this->dtoManager->create(UserDto::class, $request->all());
API Resource Handling
public function transformToArrayCollection(array $entities): array
{
return array_map([$this, 'transformToArray'], $entities);
}
Pagination component:
use Knp\Component\Pager\PaginatorInterface;
public function index(PaginatorInterface $paginator, int $page = 1)
{
$users = $paginator->paginate(
$this->userRepository->findAll(),
$page,
10
);
return $this->json($this->transformer->transformToArrayCollection($users));
}
Event-Driven Extensions
// src/EventSubscriber/TransformerSubscriber.php
use RedHotMagma\ApiBundle\Event\PreTransformEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class TransformerSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
PreTransformEvent::class => 'onPreTransform',
];
}
public function onPreTransform(PreTransformEvent $event)
{
if ($event->getEntity() instanceof User) {
$event->setData(['name' => strtoupper($event->getData()['name'])]);
}
}
}
Circular References
User ↔ Post with bidirectional relations) causes infinite loops.#[ORM\HasLifecycleCallbacks] or manually handle cycles in transformers:
public function transformToArray(User $user): array
{
$data = [
'id' => $user->getId(),
'name' => $user->getName(),
];
if (!$user->isProcessed()) { // Custom flag
$data['posts'] = $this->transformer->transformToArrayCollection($user->getPosts());
$user->setProcessed(true);
}
return $data;
}
DTO Validation Overhead
#[Assert\Callback] for dynamic validation:
use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[Assert\Callback]
public function validate(ExecutionContextInterface $context)
{
if ($this->email && !str_contains($this->email, 'example.com')) {
$context->buildViolation('Email must be from example.com')
->atPath('email')
->addViolation();
}
}
Transformer Caching
private array $cache = [];
public function transformToArray(User $user): array
{
if (!isset($this->cache[$user->getId()])) {
$this->cache[$user->getId()] = [
'id' => $user->getId(),
'name' => $user->getName(),
];
}
return $this->cache[$user->getId()];
}
Symfony vs. Laravel Quirks
AbstractBundle to bridge Laravel services:
// In RedHotMagmaApiBundle
public function boot()
{
$this->container->bind(
'dto.manager',
DtoManager::class
)->inSingleton();
}
Event Dispatcher Conflicts
services.yaml (Symfony) or AppServiceProvider (Laravel):
# config/services.yaml
services:
App\EventSubscriber\TransformerSubscriber:
tags: ['kernel.event_subscriber']
Doctrine Entity Changes
// Before: $user->getFullName()
// After: $user->getName() . ' ' . $user->getLastName()
DtoManager to add custom logic:
class CustomDtoManager extends DtoManager
{
public function create(string $dtoClass, array $data, bool $validate = true, bool
How can I help you explore Laravel packages today?