Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Presenter Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. composer.json — check the Symfony version constraint before anything else. A stale constraint against symfony/framework-bundle is the most likely reason installation fails.
  2. DependencyInjection/ — the Extension and Configuration classes tell you every config key that actually exists.
  3. Resources/config/services.* — shows what services are registered, their IDs, and whether anything is tagged or auto-configured.
  4. The presenter base class / interface — this is the whole contract; everything else is plumbing.

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.

Implementation Patterns

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.

Gotchas and Tips

  • Documentation is the source code. With zero dependents there is no Stack Overflow corpus, no blog posts, and no one to ask. Treat vendor/platim/presenter-bundle/ as the spec and pin the version in composer.json.
  • Autowiring may not work out of the box. Old bundles register services with explicit IDs and no autowire: true. If injection fails, either declare the presenter as an alias in your own services.yaml or instantiate it manually.
  • Presenters wrapping Doctrine entities can trigger lazy loading. Calling $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.
  • Do not put query logic in presenters. The moment a presenter calls a repository, it becomes untestable and stops being a presentation layer. Keep it a pure function of injected state.
  • Statefulness. A presenter holding a mutable entity will reflect later mutations. If you cache presented output, clone the entity or snapshot the scalar values first.
  • Debugging what is registered:
    bin/console debug:container --tag=platim.presenter
    bin/console debug:container 'Platim\PresenterBundle' --show-private
    
  • Compiler passes are the clean extension point. To swap the default presenter, decorate the service rather than editing vendor code:
    App\Presenter\CustomPresenter:
        decorates: platim.presenter.default
    
  • Symfony version drift is the real risk. An unmaintained bundle against a modern kernel usually fails at container compile time with a vague 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.
  • Escape by default. If presenters return HtmlString/markup, be deliberate about |raw in templates; the whole point of centralizing formatting is undermined if you then trust the output blindly.
  • Budget a decision point. Before adopting, spend an hour reading the bundle end to end. If it is more than a base class plus a compiler pass, ask whether you are buying a framework or just a naming convention.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager