starychfojtu/viewcomponent
A Laravel package for creating reusable, class-based view components, helping keep Blade templates clean and UI logic organized. It offers a simple way to define, render, and compose components across your application.
This is a Symfony bundle, not a Laravel package, so treat it like adding a third‑party service bundle to a Symfony 3.3 app: install, register the bundle in AppKernel, then use it from Twig or PHP.
composer require starychfojtu/viewcomponent
In app/AppKernel.php (Symfony 3.3 uses the old kernel layout):
$bundles = [
// ...
new Starychfojtu\ViewComponentBundle\ViewComponentBundle(),
];
Where to look first, in order:
ViewComponent base class — this defines the contract you subclass (constructor props and the method that renders/outputs the component).component(...)-style helper). Confirm the exact function name and argument order here before writing templates; it is the single most important file. As of v0.4.0 this file also defines the new cache parameter, so read the argument handling there rather than trusting the release note's one-line description.DependencyInjection/ — to see whether there is any config you can tune (template path, tag name for auto-registration, and now any cache-related defaults).First use case: build a small, obviously reusable leaf component such as an Alert or Button.
// src/AppBundle/ViewComponent/AlertComponent.php
namespace AppBundle\ViewComponent;
use Starychfojtu\ViewComponentBundle\ViewComponent;
class AlertComponent extends ViewComponent
{
private $type;
private $message;
public function __construct(string $type = 'info', string $message = '')
{
$this->type = $type;
$this->message = $message;
}
public function render(): string
{
return $this->renderTemplate('@App/view_component/alert.html.twig', [
'type' => $this->type,
'message' => $this->message,
]);
}
}
{# app/Resources/views/view_component/alert.html.twig #}
<div class="alert alert-{{ type }}">{{ message }}</div>
Then use it from any template (verify the helper name/signature against the bundle's extension):
{{ component('AppBundle\\ViewComponent\\AlertComponent', {'type': 'danger', 'message': 'Boom'}) }}
If that renders, you have the full loop working and can start moving repeated markup out of templates. Note there is an immediate parallelism for a Laravel developer: this is the conceptual ancestor of Blade components / anonymous Blade components, and of what later became symfony/ux-twig-component. Your mental model of "class + template + props" transfers directly.
Once the loop works, try the v0.4.0 caching flag on a deliberately static component (a footer, a logo lockup) before using it anywhere user-specific — and confirm the parameter's exact name and semantics from the Twig extension class first.
Props as constructor arguments. The cleanest pattern with this bundle is to make the component's constructor the prop contract: scalars, arrays, and occasionally small value objects. Keep them readonly in spirit (assign to private fields, don't mutate). This gives you typed, self-documenting components instead of a pile of Twig set statements.
Wrap, don't replace, include. Use components for things with real logic or a stable public API (buttons, alerts, cards, breadcrumbs, pagination, empty-states). Keep using plain include/embed for one-off fragments; migrating every partial to a class is churn with no payoff.
Composition over configuration. Build small components and let them contain each other by rendering inside the component's own Twig template. A CardComponent template can call component('...ButtonComponent', ...). Keep the base components dumb and let page-level templates compose them.
Cache at the leaf, not the tree (v0.4.0). The new per-call Twig parameter lets you cache a whole component's rendered output. Use it where the payoff is real and the inputs are stable: icon/logo lockups, static nav shells, marketing footers, legal disclaimers, version stamps that only change on deploy. Do not cache a component that composes other components whose output varies per request — you would be caching the composed subtree, and the invalidation story gets muddy fast.
Decide the cache scope before you turn it on. Work out from the extension class whether the cache key includes the props you pass. If it does not, a cached AlertComponent will serve the first visitor's message to everyone. When in doubt, only cache components whose render output is a pure function of the class, not the arguments — that is the safe default with a one-line feature like this.
Keep data fetching out of the component. The temptation is to inject a repository into the component and let it query. Resist it: components receive already-hydrated data, so they stay renderable in isolation and trivially testable. Do the query in the controller and pass the result in. This also keeps the caching decision understandable — a cached component with a hidden query is a debugging nightmare.
Presenter-style usage against entities. For a UserBadgeComponent, pass the User entity and let the component (or its template) decide field presentation. This is the nearest thing to a Laravel view composer, but explicit and scoped per call site rather than global. Note that per-user components are exactly the ones you must not cache.
Controller integration. For pages that are entirely one component, return the component's rendered string from a controller action rather than duplicating the template lookup. This is especially handy for AJAX/partial endpoints that return a fragment for a JS request.
Testing. Because a component is a plain PHP object with a render() method, you can unit test the non-Twig logic directly, and integration-test rendering by booting the kernel and asserting the output contains expected markers. You get a much tighter test surface than asserting against a full page render. Add at least one test that renders the same cached component twice with different props and asserts the outputs differ — that single test is your regression guard against the cache-key footgun.
Migration path. If you are starting fresh, prototype with this bundle only if you are pinned to Symfony 3.3. Otherwise go straight to symfony/ux-twig-component, which is the maintained descendant of this exact idea — the class/template/props structure is nearly the same, and it has a documented, first-class cache story, so a prototype here ports over with modest edits (and you can drop the homemade caching flag entirely).
composer.json (now 0.4.* if you want the cache feature), commit composer.lock, and be prepared to fork if PHP or Twig upgrades break it.dev but not in prod are almost always the compiled Twig cache (bin/console cache:clear --env=prod). New markup that does not appear after deploy may now additionally be the v0.4.0 render cache — check both before you start editing Twig files.services.yml, or (preferred) override the base class so both Twig and the cache pool are injected.AppKernel/app/Resources layout and the Symfony 3.x DI and Twig conventions. Do not expect it to just work on Symfony 5/6 — service definitions, twig service lookup, and bundle registration style all changed. The v0.4.0 cache layer, if it touches cache pools, is the most likely thing to break on a modern Symfony.TemplateNotFound. Bundle-relative (@Bundle/...) versus app-relative (AppBundle:dir:file.html.twig) versus app/Resources/views/... all resolve differently depending on which loader you hit. Print the path you're passing into renderTemplate() and confirm the file exists at that exact key. When in doubt, use the @Bundle/... form.ViewComponent/ namespace, mark them as services if you need DI, and avoid autowiring the world into leaf components — especially cached ones, where a dependency captured at write time may not be the one you want at read time.message/title props. Rely on Twig autoescape and use |raw only for genuinely pre-sanitized content. Don't let a raw prop cross the component boundary, and be extra careful if a cached component ever renders per-user content.ViewComponent, adding your own tag/compiler pass on top of the bundle's registration, and wrapping the Twig function with your own (the natural place to supply a default cache policy rather than repeating a flag at every call site). Don't hunt for config options that likely don't exist; the bundle is small enough to read in an afternoon, which is the recommended onboarding step.symfony/ux-twig-component or plain Twig embed. That note is what makes this safe to adopt.How can I help you explore Laravel packages today?