skills/pimcore-studio-backend-checklist/SKILL.md
Pimcore Studio migration and review checklist — 34-point verification checklist, anti-patterns to avoid, and reference base classes for validating Studio code completeness
npx skillsauth add pimcore/skills pimcore-studio-backend-checklistInstall 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.
Use this checklist before considering any migration or new Studio feature complete.
Passing nullable values to PHP filesystem functions -- in PHP 8.x with declare(strict_types=1), functions like is_file(), filesize(), unlink() throw TypeError when passed null. Always null-check before calling:
// BAD -- crashes with TypeError when $path is null
if (is_file($path)) { ... }
// GOOD -- guard against null
if ($path === null || !is_file($path)) { return []; }
// GOOD -- positive check
if ($path !== null && is_file($path)) { ... }
Assuming non-standard endpoints should be migrated -- when encountering endpoints that break Studio conventions (e.g., public-facing webhooks with Bearer token auth, different route prefixes, non-admin endpoints), always ask the user for a decision before proceeding rather than assuming they should be migrated.
Redundant runtime type checks after typed constructor params -- if a constructor already enforces a type (e.g., array $doctrineConnections), don't add runtime is_array() or similar checks. The type system handles it.
Static calls to Pimcore classes outside the bundle -- never use static calls like Pimcore\Tool::getValidLanguages(), Pimcore\Model\Asset::getById(), or any other static call to a Pimcore class that lives outside the current bundle. Always inject the corresponding resolver interface from Pimcore\Bundle\StaticResolverBundle instead. Third-party library static calls are not affected by this rule.
Using raw Request to extract query or body parameters -- never use $request->query->get(), $request->request->get(), or $request->get() in controllers. Always define a DTO class and use #[MapQueryString] (for GET) or #[MapRequestPayload] (for POST/PUT). The only exception is file uploads ($request->files->get('file')). If you encounter a situation where a DTO seems impossible, ask the user before proceeding with raw Request access.
Before considering any migration or new feature complete, verify:
final class, @internal, extends AbstractApiController.priority: 10 on static routes conflicting with wildcards.final readonly class with @internal.try/finally pattern.StaticResolverBundle resolver interfaces (third-party library static calls are exempt).$this->getParameter() to constructor injection with DI wiring.AdditionalAttributesInterface with AdditionalAttributesTrait.type: 'array' has items.#[MapQueryString] with a DTO -- no raw $request->query->get().#[MapRequestPayload] with a DTO -- no raw $request->request->get().final readonly class, all properties have defaults, validation in getters not constructors.new SomeParameters() for optional query strings.@throws: on interfaces only (exception: trait methods).@throws: short class names with proper use imports.@throws: documents all throwable exceptions including Exception.@throws types: reference Studio API exceptions, not generic PHP exceptions.use imports.@internal, @property for using-class properties, @throws on methods directly.is_file(), filesize(), etc.new Response(), #[SuccessResponse] omits content.instanceof UploadedFile, check size, include MAX_FILE_SIZE_EXCEEDED.studio_backend.yaml has bindings for all new interfaces.studio_routing.yaml exists and points to correct controller directory.| Class | Purpose |
|-------|---------|
| AbstractApiController | Base controller with jsonResponse() helper. Injects SerializerInterface. |
| AbstractPreResponseEvent | Base event for pre-response extension. Takes AdditionalAttributesInterface. |
| AdditionalAttributesInterface | Contract for DTOs that support runtime extension via key-value attributes. |
| AdditionalAttributesTrait | Default implementation of AdditionalAttributesInterface. |
| SecurityServiceInterface | getCurrentUser() to resolve the authenticated user. |
All in namespace Pimcore\Bundle\StudioBackendBundle\Exception\Api\ unless noted.
| Exception Class | HTTP Code | Use Case | Constructor |
|---|---|---|---|
| NotFoundException | 404 | Entity not found | new NotFoundException(type: 'entity', id: $id, parameter: 'id', previous: $e) |
| ForbiddenException | 403 | Permission denied | new ForbiddenException(sprintf('Access denied to "%s"', $name)) |
| ConflictException | 409 | Optimistic locking, resource state conflicts | new ConflictException($message) |
| EnvironmentException | 500 | Server/environment errors (intentional 500) | new EnvironmentException($message) |
| InvalidArgumentException | 422 | Invalid input, validation failures | new InvalidArgumentException(message: $msg, previous: $e) |
| MaxFileSizeExceededException | 413 | File too large | new MaxFileSizeExceededException($maxSize) |
| NotFoundHttpException (Symfony) | 404 | Alternative to NotFoundException | new NotFoundHttpException($message) |
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