silasyudi/restboot-bundle
A Laravel/PHP bundle for bootstrapping REST API scaffolding, offering ready-made structure for common endpoints to reduce repetitive setup. A starting point rather than a full framework. Low adoption; review the source before relying on it in production.
This release moves the package onto JMS Serializer as its serialization engine and adds first-class support for serializing arrays of objects. If you are upgrading from a v2.x install, treat this as a hard breaking change and budget time to re-audit every annotated model/DTO before deploying.
First steps:
composer require <vendor>/<package>:^3.0. JMS Serializer (jms/serializer) will be pulled in as a transitive dependency — do not also require symfony/serializer, and remove it if nothing else uses it.php artisan vendor:publish --tag=<package>-config) and review config/jms_serializer.php — metadata driver, cache dir, naming strategy, and default serialization context all live there.Serializer facade/service wrapper (usually injected as a contract or resolved via the container). That is your day-to-day entry point for serialize() / deserialize().First use case — serialize a simple DTO to JSON through the wrapper:
$json = app(Serializer::class)->serialize($user, 'json');
Then immediately test the same payload against your existing API contract. The output shape will differ from Symfony Serializer in several places (null handling, date formatting, empty collections), so diff the response before moving on.
Metadata via attributes. JMS uses its own attribute set, which is not interchangeable with Symfony's:
use JMS\Serializer\Annotation as Serializer;
class UserDto
{
#[Serializer\Groups(['api'])]
#[Serializer\SerializedName('user_id')]
public int $id;
#[Serializer\Groups(['api'])]
#[Serializer\Type("DateTimeImmutable<'Y-m-d'>")]
public \DateTimeImmutable $createdAt;
#[Serializer\Exclude]
public string $internalNote;
}
Group-based contexts. Define the shape of your payload per endpoint via groups, and pass the context at call time:
$json = $serializer->serialize($user, 'json', SerializationContext::create()->setGroups(['api']));
Prefer a small, stable set of group names (api, api.detail, admin) over ad-hoc per-controller groups.
Arrays of objects (the headline feature). The new support means you can now declare the element type and get real hydration rather than nested arrays:
#[Serializer\Type('array<App\DTO\UserDto>')]
public array $users;
or, with the property docblock:
/** @var UserDto[] */
#[Serializer\Type('array<App\DTO\UserDto>')]
public array $users;
Apply the same type hint on the receiving DTO when deserializing collections. Without an explicit element type, JMS falls back to array and you get plain arrays back — the single most common cause of "it worked in v2" confusion.
Deserialization into DTOs. Use the wrapper's deserialize() with a target class and let JMS hydrate constructors/accessors:
$dto = $serializer->deserialize($request->getContent(), UserDto::class, 'json');
If your DTOs are immutable, make sure constructor parameter names or #[SerializedName] match the incoming keys.
Integration with controllers / API resources. Wrap the serializer behind a small application service so controllers do not talk to JMS directly. This gives you one place to set default groups and context, and keeps the eventual next serializer swap cheap.
Versioned payloads. If you expose public APIs, use #[Since('3.0')] / #[Until('4.0')] and set the version on the context rather than branching in code.
Custom handlers for value objects. Register handlers (e.g. money, UUID, enums) in the config's handler list rather than adding __serialize hacks to domain objects.
#[Groups], #[SerializedName], and #[Ignore] from Symfony\Component\Serializer\Annotation do nothing under JMS. Worse, JMS exports everything by default, so properties you thought were excluded may now leak. Audit all models before shipping.#[Exclude], all public properties/accessors are serialized. Lock this down with a default group or an explicit exclusion policy in config.null handling changed. Symfony tended to emit "key": null; JMS omits nulls unless serialize_null is enabled on the context. Flip it explicitly per-endpoint if clients expect nulls.Y-m-d\TH:i:sP (or ISO variants). If your API contract had a different format, set #[Type("DateTimeImmutable<'format'>")] or configure the handler..gitignore and writable.#[Type] declaration matter; a docblock alone may be picked up by one driver but not another. Verify with a round-trip test (deserialize(serialize($x)) == $x).{}. An empty PHP array serializes to [] in JSON; if a client expects an object, you will need a wrapper handler or a collection type.MissingConstructorArgumentsException. Either default the parameters or mark them optional.#[MaxDepth] or #[Exclude] on the back-reference. Set enable_max_depth in the context if you rely on it.#[SerializedName] still wins — but you can end up with a mixed casing contract. Pick one convention and enforce it in a test.ArrayCache outside tests.symfony/serializer if unused, re-annotate all DTOs to JMS attributes, re-verify JSON contracts with a golden-file test, add array-of-object element types, and clear/warm the metadata cache before rolling out.How can I help you explore Laravel packages today?