Installation
composer require dayploy/js-dto-bundle
Add the bundle to your config/bundles.php:
Dayploy\JsDtoBundle\DayployJsDtoBundle::class => ['all' => true],
Basic Setup Run migrations (if any) and clear cache:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
php bin/console cache:clear
First Use Case Define a DTO (Data Transfer Object) in PHP:
namespace App\Dto;
use Dayploy\JsDtoBundle\Annotation\JsDto;
#[JsDto]
class UserDto
{
public string $name;
public int $age;
}
Generate the JavaScript counterpart:
php bin/console js-dto:generate
The bundle will create a UserDto.js file in public/js/dtos/ with TypeScript types and serialization logic.
DTO Definition
#[JsDto]) or attributes to mark classes as DTOs.string, int, array, DateTime).#[JsDto]
class AddressDto {
public string $street;
public string $city;
}
#[JsDto]
class UserDto {
public string $name;
public AddressDto $address;
}
Serialization/Deserialization
// Auto-generated in UserDto.js
export function serialize(userDto: UserDto): string {
return JSON.stringify(userDto);
}
export function deserialize(json: string): UserDto {
return JSON.parse(json) as UserDto;
}
Integration with Controllers
import { UserDto, deserialize } from './js/dtos/UserDto';
fetch('/api/users/1')
.then(response => response.json())
.then(json => {
const user: UserDto = deserialize(json);
console.log(user.name); // Type-safe access
});
API Response Handling
use App\Dto\UserDto;
public function show(User $user)
{
$dto = new UserDto();
$dto->name = $user->name;
$dto->age = $user->age;
return response()->json($dto);
}
Frontend State Management
const userState = reactive({
user: null as UserDto | null,
});
Custom Serialization
Override default behavior by implementing JsonSerializable in your DTO:
#[JsDto]
class CustomUserDto implements JsonSerializable
{
public function jsonSerialize(): array
{
return [
'full_name' => $this->name . ' ' . $this->surname,
'age' => $this->age,
];
}
}
Excluding Properties
Use #[JsDto\Exclude] to skip properties during serialization:
#[JsDto]
class UserDto {
public string $name;
#[JsDto\Exclude]
public string $password;
}
Dynamic DTOs
For dynamic properties (e.g., from databases), use array or stdClass:
#[JsDto]
class DynamicDto {
public array $metadata;
}
Frontend API Client Combine with API clients (e.g., Axios) for consistent type safety:
import axios from 'axios';
import { UserDto, deserialize } from './js/dtos/UserDto';
const api = axios.create({ baseURL: '/api' });
async function fetchUser(id: number): Promise<UserDto> {
const response = await api.get(`/users/${id}`);
return deserialize(response.data);
}
Testing Mock DTOs in PHPUnit:
$dto = new UserDto();
$dto->name = 'John';
$dto->age = 30;
$serialized = json_encode($dto);
$this->assertEquals('{"name":"John","age":30}', $serialized);
Caching Issues
php bin/console js-dto:clear-cache
php bin/console js-dto:generate --force
Type Mismatches
DateTime in PHP becomes string in JS by default).#[JsDto\Type]:
#[JsDto]
class EventDto {
#[JsDto\Type('Date')]
public DateTime $date;
}
Circular References
UserDto referencing OrderDto which references UserDto).#[JsDto\Exclude] or break cycles manually.Namespace Conflicts
namespace App\Dto\Users;
Database Migrations
#[JsDto]
class ProductDto {
public string $sku;
public string $name;
// Add new fields here before migrating
}
Generated JS Inspection
public/js/dtos/ for generated files to verify output.deserialize failures).PHP Annotations
php bin/console js-dto:validate
Logging
config/packages/dayploy_js_dto.yaml:
debug: true
tail -f var/log/dev.log
Custom Generators
Dayploy\JsDtoBundle\Generator\DtoGeneratorInterface:
namespace App\Generator;
use Dayploy\JsDtoBundle\Generator\DtoGeneratorInterface;
class CustomDtoGenerator implements DtoGeneratorInterface
{
public function generate(string $className, array $properties): string
{
// Custom logic
return '// Custom generated code';
}
}
config/packages/dayploy_js_dto.yaml:
generators:
custom: App\Generator\CustomDtoGenerator
Post-Generation Hooks
// src/EventListener/DtoGenerationListener.php
use Dayploy\JsDtoBundle\Event\DtoGeneratedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DtoGenerationListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
DtoGeneratedEvent::class => 'onDtoGenerated',
];
}
public function onDtoGenerated(DtoGeneratedEvent $event)
{
$content = $event->getContent();
$content .= '\n// Custom post-processing';
$event->setContent($content);
}
}
Custom Templates
templates/bundles/dayployjsdto/:
{# Custom template for DTO generation #}
export interface {{ className }} {
{% for property in properties %}
{{ property.type }} {{ property.name }}{% if not loop.last %},{% endif %}
{% endfor %}
}
Conditional Generation
ignore_classes:
- 'App\Dto\InternalDto'
- 'App\Dto\TempDto'
How can I help you explore Laravel packages today?