braunstetter/template-hooks-bundle
Installation:
composer require braunstetter/template-hooks-bundle
Add to config/bundles.php (Symfony) or register the bundle in config/app.php (Laravel via Symfony components):
Braunstetter\TemplateHooksBundle\BraunstetterTemplateHooksBundle::class => ['all' => true],
First Hook Usage:
In your Twig template (e.g., base.html.twig), insert the hook tag:
{{ hook('app.cp.global-header') }}
Create a Hook Class:
Generate a hook class (e.g., app/Twig/BreadcrumbsHook.php):
namespace App\Twig;
use Braunstetter\TemplateHooks\Twig\TemplateHook;
class BreadcrumbsHook extends TemplateHook {
public function render(): string {
return $this->templating->render('hooks/breadcrumbs.html.twig', $this->context);
}
public function setTarget(): string|array {
return 'app.cp.global-header';
}
}
Register the Hook:
Add the hook to the service container (e.g., in config/services.php or via a service provider):
$this->container->set('app.twig.breadcrumbs_hook', function () {
return new \App\Twig\BreadcrumbsHook();
});
Dynamic Header Injection:
Extend a base template (e.g., base.html.twig) with a hook for third-party bundles or modules to inject content (e.g., breadcrumbs, notifications) without modifying the core template. Example:
<!-- base.html.twig -->
<header>
{{ hook('app.cp.global-header') }}
</header>
A bundle can then add its own header content via a hook class.
Define Hooks in Templates:
Use {{ hook('namespace.target') }} in Twig templates to mark insertion points. Example:
<div class="sidebar">
{{ hook('app.cp.sidebar-widgets') }}
</div>
Create Hook Classes:
Extend TemplateHook and implement render() and setTarget():
class SidebarWidgetHook extends TemplateHook {
public function render(): string {
return $this->templating->render('hooks/sidebar_widgets.html.twig', [
'widgets' => $this->getWidgets(),
]);
}
public function setTarget(): string {
return 'app.cp.sidebar-widgets';
}
}
Pass Context Data:
Override getContext() to inject dynamic data:
public function getContext(): array {
return [
'user' => $this->getUserService()->getCurrentUser(),
];
}
Register Hooks: Bind hook classes to the container (e.g., via a service provider):
$this->container->set('app.twig.sidebar_widget_hook', function () {
return new SidebarWidgetHook();
});
Laravel-Specific: Use Laravel’s service container to register hooks:
public function register() {
$this->app->singleton(\App\Twig\SidebarWidgetHook::class);
}
Bind the hook to Twig’s service:
$this->app->afterResolving('twig', function ($twig) {
$twig->addExtension(new \Braunstetter\TemplateHooksBundle\Twig\TemplateHookExtension());
});
Dynamic Hooks: Register hooks conditionally (e.g., based on user roles or features):
if ($this->app['config']['features.sidebar_widgets']) {
$this->app->singleton(SidebarWidgetHook::class);
}
Reusable Hooks: Create abstract base hooks for shared functionality:
abstract class BaseHook extends TemplateHook {
protected function getSharedData(): array {
return ['app_name' => config('app.name')];
}
}
Testing: Mock hooks in tests by overriding the container:
$this->app->instance(
\App\Twig\BreadcrumbsHook::class,
Mockery::mock(\App\Twig\BreadcrumbsHook::class)
);
Hook Registration Timing:
boot() method or use afterResolving.Context Overrides:
render():
$context = array_merge($this->getContext(), $this->context);
Circular Dependencies:
if (!$this->hasRendered()) {
$this->render();
}
Namespace Collisions:
'app.header') across bundles.'bundle1.app.header', 'bundle2.app.header').Missing Hooks:
php bin/console debug:container | grep hook
TemplateHook.Render Errors:
render() in a try-catch to log errors:
public function render(): string {
try {
return $this->templating->render('hooks/...', $this->context);
} catch (\Exception $e) {
\Log::error('Hook render error: ' . $e->getMessage());
return '';
}
}
Performance:
public function render(): string {
return cache()->remember('hook.app.header', 3600, function () {
return $this->templating->render('...');
});
}
Custom Hook Logic:
Extend TemplateHook to add pre/post-render hooks:
class CustomHook extends TemplateHook {
public function preRender() {
// Logic before rendering
}
public function postRender(string &$output) {
// Modify output
}
}
Hook Prioritization:
Implement a priority system in setTarget():
public function setTarget(): array {
return [
'app.cp.global-header' => ['priority' => 10], // Lower = higher priority
];
}
Dynamic Hook Discovery:
Auto-register hooks from a directory (e.g., app/Hooks/):
$hookClasses = glob(app_path('Hooks/*Hook.php'));
foreach ($hookClasses as $class) {
$this->app->singleton($class);
}
Conditional Hooks: Use traits to enable/disable hooks dynamically:
trait ConditionalHook {
public function isEnabled(): bool {
return $this->app['config']['features.hooks'];
}
public function render(): string {
return $this->isEnabled() ? parent::render() : '';
}
}
How can I help you explore Laravel packages today?