Weave Code
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

Template Hooks Bundle Laravel Package

braunstetter/template-hooks-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. 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],
    
  2. First Hook Usage: In your Twig template (e.g., base.html.twig), insert the hook tag:

    {{ hook('app.cp.global-header') }}
    
  3. 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';
        }
    }
    
  4. 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();
    });
    

First Use Case

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.


Implementation Patterns

Core Workflow

  1. 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>
    
  2. 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';
        }
    }
    
  3. Pass Context Data: Override getContext() to inject dynamic data:

    public function getContext(): array {
        return [
            'user' => $this->getUserService()->getCurrentUser(),
        ];
    }
    
  4. 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();
    });
    

Integration Tips

  • 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)
    );
    

Gotchas and Tips

Pitfalls

  1. Hook Registration Timing:

    • Issue: Hooks may not render if registered after Twig initialization.
    • Fix: Register hooks in a service provider’s boot() method or use afterResolving.
  2. Context Overrides:

    • Issue: Multiple hooks targeting the same point may override context data unexpectedly.
    • Fix: Merge contexts explicitly in render():
      $context = array_merge($this->getContext(), $this->context);
      
  3. Circular Dependencies:

    • Issue: Hooks relying on other hooks may cause infinite loops if not careful.
    • Fix: Use lazy-loading or guard clauses:
      if (!$this->hasRendered()) {
          $this->render();
      }
      
  4. Namespace Collisions:

    • Issue: Reusing hook names (e.g., 'app.header') across bundles.
    • Fix: Use unique namespaces (e.g., 'bundle1.app.header', 'bundle2.app.header').

Debugging

  1. Missing Hooks:

    • Debug: Check if the hook class is bound to the container. Use:
      php bin/console debug:container | grep hook
      
    • Fix: Ensure the service is registered and the class extends TemplateHook.
  2. Render Errors:

    • Debug: Wrap 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 '';
          }
      }
      
  3. Performance:

    • Issue: Complex hooks may slow down template rendering.
    • Fix: Cache hook outputs if static:
      public function render(): string {
          return cache()->remember('hook.app.header', 3600, function () {
              return $this->templating->render('...');
          });
      }
      

Extension Points

  1. 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
        }
    }
    
  2. Hook Prioritization: Implement a priority system in setTarget():

    public function setTarget(): array {
        return [
            'app.cp.global-header' => ['priority' => 10], // Lower = higher priority
        ];
    }
    
  3. 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);
    }
    
  4. 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() : '';
        }
    }
    
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.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware