platim/presenter-bundle
A presenter bundle for PHP applications that wraps your models and domain objects with dedicated presentation classes, keeping views and templates clean. Move formatting, display logic, and view helpers out of your entities and into reusable, testable presenters.
composer require platim/presenter-bundle
Because this bundle has effectively zero adoption, assume there is no Flex recipe and no hosted docs. Budget the first 15 minutes for reading source, in this order:
composer.json — check the Symfony version constraint before anything else. A stale constraint against symfony/framework-bundle is the most likely reason installation fails.DependencyInjection/ — the Extension and Configuration classes tell you every config key that actually exists.Resources/config/services.* — shows what services are registered, their IDs, and whether anything is tagged or auto-configured.Register it manually if Flex does not do it for you:
// config/bundles.php
return [
// ...
Platim\PresenterBundle\PlatimPresenterBundle::class => ['all' => true],
];
First use case: move formatting logic out of Twig and controllers by wrapping a domain object in a presenter.
// before: {{ user.createdAt|date('Y-m-d') }} {{ user.firstName }} {{ user.lastName }}
// after: {{ user.present().createdOn }} {{ user.present().fullName }}
The payoff is testable, mockable formatting logic — especially valuable when the same entity is rendered in HTML, JSON API, and email templates with different rules.
One presenter per entity, injected with what it needs. The presenter is a service, so give it the router, translator, and clock via constructor injection rather than reaching for globals:
final class UserPresenter
{
public function __construct(
private User $user,
private RouterInterface $router,
private TranslatorInterface $translator,
) {}
public function fullName(): string
{
return trim("{$this->user->getFirstName()} {$this->user->getLastName()}");
}
public function profileUrl(): string
{
return $this->router->generate('user_show', ['id' => $this->user->getId()]);
}
}
Controller hands over presenters, not entities. This keeps the template dumb and makes the controller's output shape explicit:
public function show(User $user): Response
{
return $this->render('user/show.html.twig', [
'user' => $this->presenterFactory->create($user),
]);
}
A factory or a present() method on the entity. If the bundle does not ship a factory, write a thin one — it is the seam that lets you swap presenters per context (e.g. AdminUserPresenter vs PublicUserPresenter) without touching entities.
Collections. Present a list once at the boundary, not per item inside the template:
$presented = array_map($this->presenterFactory->create(...), $users);
Autoconfigure presenters via a tag if the bundle defines one (look for registerForAutoconfiguration in the extension). Otherwise add it yourself in services.yaml:
App\Presenter\:
resource: '../src/Presenter/'
tags: ['platim.presenter']
Test presenters in isolation. Because they are plain services over a value object, no kernel is needed:
$presenter = new UserPresenter($user, $routerStub, $translatorStub);
self::assertSame('Jane Doe', $presenter->fullName());
API vs HTML. Instantiate (or select) a different presenter for the JSON representation so serialization concerns never leak into the Twig-facing one.
vendor/platim/presenter-bundle/ as the spec and pin the version in composer.json.autowire: true. If injection fails, either declare the presenter as an alias in your own services.yaml or instantiate it manually.$presenter->fullName() inside a loop over 200 users will fire queries if the presenter touches an association. Batch-load in the repository and assert query counts in tests.bin/console debug:container --tag=platim.presenter
bin/console debug:container 'Platim\PresenterBundle' --show-private
App\Presenter\CustomPresenter:
decorates: platim.presenter.default
ServiceNotFoundException or a Class not found in services.xml. If that happens, evaluate whether a 200-line in-house presenter layer is cheaper than patching the bundle — for a package this small, it often is.HtmlString/markup, be deliberate about |raw in templates; the whole point of centralizing formatting is undermined if you then trust the output blindly.How can I help you explore Laravel packages today?