toro/widget-bundle
toro/widget-bundle provides a flexible widget system for PHP applications. Define reusable widget types, assign them to configurable areas, and render them in templates with caching and straightforward configuration for CMS-style content blocks.
Read the environment before the code. This is a Symfony2 bundle, not a Laravel package. It ships no ServiceProvider, no config/ publish tag, no Facade, and it pre-dates Composer auto-discovery. composer require toro/widget-bundle will succeed, but nothing in a Laravel app will register or boot it. The last release was July 2017 against the Symfony 2.x/3.x line, so treat it as a reference implementation rather than a dependency.
First places to look, in order:
composer.json — the symfony/symfony (or symfony/framework-bundle) constraint and required PHP version tell you immediately whether it can even resolve on your stack.ToroWidgetBundle.php — bundles of this kind do their real work in build() via compiler passes; that's where widget types get registered into a registry.DependencyInjection/Configuration.php — the config tree (likely a toro_widget: root) defines every knob you can tune.Resources/config/*.yml — the service wiring, the tag name used for widget discovery, and any Twig extension registration.Resources/views/ — the Twig templates that actually render widgets.First use case, honestly stated: the only environment where this runs as designed is a throwaway Symfony 2.8/3.x app on PHP 5.6–7.1. Register the bundle in AppKernel, add the config block, and render a widget in a Twig template. If you can't reproduce that in 30 minutes, stop — you've confirmed the maintenance cost is higher than reimplementing the pattern.
For the Laravel codebase you actually ship: do not install it. Read it as a spec for a widget/block system and port the concept to Blade + the service container (see below). If you are genuinely forced into a hybrid (legacy Symfony service you must consume), expose widget rendering behind an HTTP endpoint and call it from Laravel — never try to boot Symfony's DI inside Laravel.
The bundle's design is the standard Symfony recipe for a pluggable widget system. Each piece has a direct Laravel equivalent, and mapping them is the fastest way to port.
1. Widget registry instead of a compiler pass. Symfony discovers widgets by service tag; a compiler pass collects them into a registry. In Laravel, bind a singleton and register explicitly during boot:
// app/Providers/WidgetServiceProvider.php
public function register(): void
{
$this->app->singleton(WidgetRegistry::class, fn () => new WidgetRegistry());
}
public function boot(): void
{
$registry = $this->app->make(WidgetRegistry::class);
$registry->register('latest-posts', LatestPostsWidget::class);
$registry->register('promo-banner', PromoBannerWidget::class);
}
2. A widget contract mirroring the bundle's base class.
The Symfony side almost certainly exposes a render(array $options): string style method. Keep that shape so the port stays a mechanical translation:
interface Widget
{
public function render(array $options = []): View;
}
class LatestPostsWidget implements Widget
{
public function __construct(private PostRepository $posts) {}
public function render(array $options = []): View
{
return view('widgets.latest-posts', [
'posts' => $this->posts->latest($options['limit'] ?? 5),
]);
}
}
3. Rendering surface: Twig function → Blade component or helper.
The bundle wires a Twig function (likely {{ toro_widget('name', {...}) }}). The Laravel equivalent is a Blade component or a small helper:
// app/Support/helpers.php
function widget(string $name, array $options = []): Htmlable
{
return app(WidgetRegistry::class)->render($name, $options);
}
{{-- resources/views/widgets/slot.blade.php --}}
{!! widget($name, $options ?? []) !!}
A Blade component (<x-widget name="latest-posts" :limit="5" />) is the cleaner fit if you want prop validation and attribute bagging.
4. Configuration: YAML tree → published config file.
Port Configuration.php into config/widgets.php, publish it with php artisan vendor:publish, and read it via config('widgets.*'). Keep the same nesting so operators migrating from the Symfony app recognize the keys.
5. Integration tips.
Cache::remember keyed by widget name + options hash.$this->get('/')->assertSee(...)) — the ported system has no upstream test coverage to lean on.symfony/* packages and often with PHP 8. Resist --ignore-platform-reqs; you'll get runtime fatals from removed Symfony APIs (notably the Symfony 4 DI rewrite) rather than a clean failure.bin/console debug:container --tag=<widget_tag> and debug:event-dispatcher tell you whether a widget registered; Laravel's php artisan about and container dumps are useless for the bundle itself.register() + iteration order) so widget precedence and overriding behave the same way.Configuration.php will throw on unknown keys; Laravel's config arrays silently ignore typos. Add validation in your provider or a config test, or you'll ship a config that looks applied but isn't.$options.ToroWidgetBundle.php and the config tree you find the feature set is "render named partial with options," port it in an afternoon. That is very likely the whole package.How can I help you explore Laravel packages today?