Darkwood · Symfony · 2026
Overview
Why it matters
Workers and CLI boot a lean kernel without HttpKernel baggage.
Messenger throughput, decode recovery, and cross-service contracts.
High leverage for long-running processes, APIs, and operational tooling.
HTTP-less applications
AbstractKernel + KernelTrait replace MicroKernelTrait for workers, consumers, and CLI-only apps.HTTP-less · architecture
flowchart TB
subgraph ep [Entry points]
H[HTTP]
C[Console]
M[Messenger consumer]
end
K[AbstractKernel + KernelTrait]
D[DI Container]
H --> K
C --> K
M --> K
K --> D
Backend · VarExporter
Typical object graphs; up to 15× on heavy property sets.
toArray() / fromArray() - ~30–40% smaller than serialize.
Container compile, Form snapshots, ArrayAdapter cache.
$clone = (new DeepCloner($graph))->clone();
$payload = (new DeepCloner($graph))->toArray();
Dependency Injection
Refresh DB URLs and secrets via resetEnvCache() - no rebuild.
Declarative decorates / decorates_tag - drop custom compiler passes.
Parameter-name alias matching deprecated - Symfony 9.0.
DI · code
public function __construct(
#[Autowire(env: 'DB_URL')] private \Closure $dbUrl,
#[Target('image')] private StorageInterface $storage,
) {}
Dynamic controller attributes
_controller_attributes stored after first resolve - listeners override #[Cache], #[IsGranted], custom attrs.Dedicated events: kernel.controller_arguments.{AttributeFQCN}
API layer
Multipart files in DTOs · variadic unpacking · dynamic validation groups.
Return objects - Symfony builds Response + Content-Type + context.
Async · Messenger
php bin/console messenger:consume async \
--fetch-size=8 --no-reset=100
--fetch-size - batch round-trips--no-reset - balance state vs speedAsync · reliability
flowchart LR Q[Transport] --> W[Worker] W -->|decode fail| R[Retry / failure pipeline] W -->|ok| H[Handler] M[serializedTypeName] -.->|header| Q
Decode failures route through retry/failure transports - not silently dropped.
Serialization · JSON
ValueObjectTransformerInterface - compact domain scalars · timezone-aware DateTime.
#[AsJsonPathFunction] - domain filters without preprocessing pipelines.
Relevant for APIs, log streams, RAG document stores.
CLI & DX
public function __invoke(
#[Argument, MapEntity] User $user,
#[Option, MapDateTime(format: 'Y-m-d')] \DateTimeInterface $date,
#[Autowire(service: 'messenger.bus.async')] MessageBusInterface $bus,
): int {}
CLI & DX
Prompts + Validator constraints · image paste via InputFile.
DTO validation like #[MapRequestPayload].
Multiple commands per class · shared constructor deps.
Workers · AI · orchestration
flowchart TB CLI[CLI commands\nresolvers + MapInput] --> BUS[Messenger bus] BUS --> W[HTTP-less consumer\nfetch-size · no-reset] W --> S[Services via DI\nenv Closure · DeepCloner] W --> OUT[JSON stream / polyglot type header] AI[AI / RAG pipelines] --> CLI AI --> W
Validator
#[Assert\Xml] - XSD validation with line-numbered violationsMockClock in testsvalidateInContext() - reentrant validators (nested DTOs)enablePropertyMetadataExistenceCheck() - catch property typosMost impactful
Takeaway
Upgrade path is incremental - adopt per entry point, not big-bang.
Conclusion
References · Darkwood