skills/pimcore-studio-backend-dto/SKILL.md
Pimcore Studio DTO, Schema, Hydrator, and Event patterns — response DTOs with AdditionalAttributes, parameter DTOs, query DTOs, OpenAPI schema annotations, hydrator structure, and pre-response events
npx skillsauth add pimcore/skills pimcore-studio-backend-dtoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The StudioBackendBundle uses a layered architecture: Controllers (HTTP only) -> Services (business logic) -> Hydrators (DTO creation). Events provide extension points. All APIs are OpenAPI-documented.
DTOs carry data between layers. Hydrators construct DTOs from raw data. Events are dispatched before returning response DTOs, providing extension points for third-party code.
#[Schema(
schema: 'BundleDataImporterDataPreviewResponse',
title: 'Bundle Data Importer Data Preview Response',
required: ['dataPreview', 'previewRecordIndex'],
type: 'object'
)]
final class DataPreviewResponse implements AdditionalAttributesInterface
{
use AdditionalAttributesTrait;
public function __construct(
#[Property(description: '...', type: 'array', items: new Items(...))]
private readonly array $dataPreview,
#[Property(description: '...', type: 'integer', example: 0)]
private readonly int $previewRecordIndex,
) {
}
// getters...
}
AdditionalAttributesInterface and use AdditionalAttributesTrait.BundleDataImporter{Name} (matches the bundle name).required array in #[Schema] lists all non-nullable, non-defaulted properties.type: 'array' must always have items:
// GOOD
#[Property(type: 'array', items: new Items(type: 'string'))]
#[Property(type: 'array', items: new Items(type: 'object'))]
#[Property(type: 'array', items: new Items(properties: [...], type: 'object'))]
// BAD -- missing items
#[Property(type: 'array')]
Items from OpenApi\Attributes\Items when using array types.readonly on all constructor-promoted properties.final class (not final readonly class) because AdditionalAttributesTrait has a mutable $additionalAttributes array.@internal PHPDoc on all DTO classes.Schema\{Name}Response (e.g., DataPreviewResponse)ConfigurationDetail)#[Schema(
schema: 'BundleDataImporterLoadPreviewParameters',
title: 'Bundle Data Importer Load Preview Parameters',
type: 'object'
)]
final readonly class LoadPreviewParameters
{
public function __construct(
#[Property(description: '...', type: 'object', nullable: true)]
private ?array $currentConfig = null,
#[Property(description: '...', type: 'integer', example: 0)]
private int $recordNumber = 0,
) {
}
// getters...
}
Parameter DTOs don't need AdditionalAttributesInterface and can be final readonly class.
Schema\{Name}Parameters (e.g., LoadPreviewParameters)No OpenAPI attributes needed on the class -- the controller adds #[TextFieldParameter], #[BoolParameter], #[IntParameter], etc. individually per query param:
final readonly class ClassAttributeParameters
{
public function __construct(
private ?string $classId = null,
private bool $loadAdvancedRelations = false,
) {
}
// getters...
}
For endpoints with pagination, sorting, and filtering:
final readonly class ClassificationStoreKeyParameters
{
public function __construct(
private ?string $classId = null,
private ?string $fieldName = null,
private ?string $transformationResultType = null,
private ?string $sort = null, // JSON-encoded sort config
private int $start = 0, // offset, not page number
private int $limit = 15,
private ?string $searchfilter = null,
private ?string $filter = null,
) {
}
// getters...
}
Key conventions:
$start (offset) and $limit (count), not $page/$perPage.?string (JSON-encoded), not a structured type.max(1, $this->page)).$parameters = new SomeParameters().Controller per-param attributes:
#[TextFieldParameter(name: 'classId', description: '...', required: false, example: 'Car')]
#[IntParameter(name: 'start', description: '...', required: false, example: 0)]
#[IntParameter(name: 'limit', description: '...', required: false, example: 15)]
#[TextFieldParameter(name: 'sort', description: '...', required: false, example: null)]
final readonly class PreviewHydrator implements PreviewHydratorInterface
{
public function __construct(
private SecurityServiceInterface $securityService,
private PreviewService $previewService,
private InterpreterFactory $interpreterFactory
) {
}
public function hydrateDataPreview(array $dataPreview, int $recordNumber): DataPreviewResponse
{
return new DataPreviewResponse($dataPreview, $recordNumber);
}
}
final readonly class with @internal.loadAvailableColumnHeaders, isValidJson) when that logic is needed by both services and hydrators.{Domain}HydratorInterface / {Domain}HydratorHydrator\hydrate{DtoName} (e.g., hydrateDataPreview, hydrateImportStart).final class DataPreviewEvent extends AbstractPreResponseEvent
{
public const string EVENT_NAME = 'pre_response.data_importer.data_preview';
public function __construct(
private readonly DataPreviewResponse $dataPreview
) {
parent::__construct($dataPreview);
}
public function getDataPreview(): DataPreviewResponse
{
return $this->dataPreview;
}
}
AbstractPreResponseEvent from StudioBackendBundle.EVENT_NAME constant follows pattern: pre_response.{bundle_name}.{snake_case_dto_name}.const string for the event name.AdditionalAttributesInterface).@internal -- events are public extension points (public API).Event\Studio\PreResponse\{ResponseName}Event (e.g., ConfigurationDetailEvent for ConfigurationDetail DTO).Events MUST be dispatched before returning every response DTO:
$response = $this->hydrator->hydrateSomething(...);
$this->eventDispatcher->dispatch(new SomeEvent($response), SomeEvent::EVENT_NAME);
return $response;
tools
UX and UI design conventions for Pimcore Studio - layout, spacing, action labels, writing style, and design principles for consistent extensions
tools
Widget system in Pimcore Studio UI - registering widgets, opening them in layout areas, WidgetManagerTabConfig, and connecting widgets to navigation
tools
How bundles consume the Pimcore Studio UI SDK - plugins, modules, DI, registries, and imports
development
TypeScript coding standards and best practices for Pimcore Studio UI - type safety, null checks, and code quality