bd-presenter
better-data: Extending the Presenter
For library maintainers adding output transformations to the better-data Presenter — a new fluent builder method (mask, formatPhone, hideIfBlank), a PresentationContext flag (admin vs REST vs export), or a Formatter helper. The Presenter is the read-side projection layer between a DataObject and whatever consumes it (admin UI, REST response, audit log).
Misconception this skill corrects
"The Presenter is
readonlylike the DataObject — every fluent method must clone and return a new instance."
Wrong. The DataObject is readonly, the Presenter is intentionally a mutable builder. Look at src/Presenter/Presenter.php:65-89 — class Presenter (not final readonly class), with private mutable properties $only, $hidden, $rename, $computed, $presets, $includeSensitive. Each fluent method assigns to $this->... and returns $this. This is deliberate — chained calls share state, repeated calls override, and there's no clone overhead.
What IS immutable: $this->dto is protected readonly DataObject and never gets reassigned. The builder mutates ITS state to control HOW the DTO is rendered; the DTO itself stays untouched.
The "don't mutate permanently" rule from older docs means: don't introduce a method whose effect can't be reset by a subsequent method or context swap. A method that pushes to a private array is fine; a method that writes to a static cache or to the wrapped DTO would be a regression.
Other AI-prone misconceptions:
- "I'll add
getSecretRevealed()so consumers don't need->reveal()boilerplate." Wrong — the explicit$dto->field->reveal()inside acompute()closure IS the security audit point. Adding a bypass method is the same regression as a debug-mode log of secrets. - "CollectionPresenter is a separate Presenter; I just add the method there too with copy/paste logic." Wrong —
CollectionPresenterrecords each configurer as a closure on$this->configurers(src/Presenter/CollectionPresenter.php:30-44) and replays it on every item via the per-item Presenter. The pattern is$this->configurers[] = static fn (Presenter $p) => $p->yourMethod(...);. No business logic on the collection side.