| name | pimcore-studio-backend-code-style |
| description | Pimcore Studio PHP code style rules — strict types, class modifiers, final/readonly patterns, formatting (120 chars), named arguments, imports, PHPDoc, @throws documentation, and constructor promotion |
| metadata | {"audience":"pimcore-developers","focus":"backend"} |
Architecture Context
The StudioBackendBundle uses a layered architecture: Controllers (HTTP only) -> Services (business logic) -> Hydrators (DTO creation). Events provide extension points. All APIs are OpenAPI-documented.
These code style rules apply to ALL PHP code written for Studio bundles.
Strict Types
Every PHP file starts with:
<?php
declare(strict_types=1);
Class Modifiers
| Class Type | Modifier | Why |
|---|
| Service implementation | final readonly class | No inheritance, immutable after construction |
| Hydrator implementation | final readonly class | Same |
| Controller | final class | Cannot be readonly (extends AbstractApiController with mutable state) |
| Response/Parameter DTO | final class or final readonly class | final class when using AdditionalAttributesTrait (has mutable array); final readonly class for parameter-only DTOs |
| Event | final class | Extends AbstractPreResponseEvent (has mutable state) |
| Interface | interface | Standard |
Internal Annotation
All classes except events get @internal in their PHPDoc:
Events are public API (no @internal).
Constructor Promotion
Always use constructor promotion for all injected dependencies and DTO properties:
public function __construct(
private readonly ConfigurationServiceInterface $configurationService
) {
}
Readonly Properties
Use readonly on every property that is never reassigned. Prefer final readonly class when all properties are readonly and the class has no traits with mutable state.
Formatting
- Max 120 characters per line (PSR-12). No exceptions.
- No double blank lines anywhere in the file.
- Trailing comma after the last parameter in multi-line constructor/method calls.
- Single blank line between methods; single blank line before
return in multi-statement methods.
Named Arguments
Do NOT use named arguments unless you need to skip positional parameters:
$this->hydrator->hydrateKeyName(groupName: $group->getName(), keyName: $key->getName());
$this->hydrator->hydrateKeyName(keyId: null, groupName: $group->getName(), keyName: $key->getName());
$this->service->doSomething(name: $name, config: $config);
Imports
- Always use
use imports. Never use FQCNs inline in code, PHPDoc, or @throws.
- Sort imports alphabetically.
- Remove unused imports immediately.
PHPDoc
Default Values
Remove redundant default values at call sites. If the method signature has $param = null and you'd pass null, omit the argument (use named args if needed to skip):
$this->hydrator->hydrateKeyName(null, $group->getName(), $key->getName());
$this->hydrator->hydrateKeyName(groupName: $group->getName(), keyName: $key->getName());
@throws Documentation
- Document
@throws on interfaces only, not on implementations. Implementations inherit the contract.
- Use short class names, never FQCNs in
@throws:
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
- Document every exception that a method can throw, including
\Exception. Import Exception via use Exception; and reference it as @throws Exception (short name, not FQCN).
- Add
@throws for specific vendor exceptions that callers should be aware of (e.g., InvalidConfigurationException, QueueNotEmptyException).
- Import all exception classes used in
@throws via use statements at the top of the file -- this includes Exception itself.
- Exception to the "interfaces only" rule: Trait methods get
@throws directly on the trait methods, since traits have no interfaces.