A lightweight, modular hook engine for Laravel that enables extensibility in both backend processes and Blade templates. Create flexible, plugin-like architectures with minimal overhead.
Based on esemve/Hook by Bence Kádár.
- 🎯 Simple Hook Registration - Register listeners with priorities
- 🔄 Blade Directives - Use
@shookfor inserting content and@hook/@endhookfor wrapping/modifying content - 🎨 Content Modification - Wrap and modify HTML sections
- 🧪 Testing Support - Mock hooks for easy testing
- 📊 Hook Discovery - List all registered and available hooks
composer require realzone22/larahooksPublish the configuration file (optional):
php artisan vendor:publish --tag="larahooks-config"Register a hook listener in your AppServiceProvider:
use RealZone22\LaraHooks\Facades\LaraHooks;
public function boot(): void
{
LaraHooks::listen('user.created', function ($callback, $output, $params) {
$user = $params['user'];
// Send welcome email, create profile, etc.
}, 10);
}Execute the hook in your code:
use RealZone22\LaraHooks\Facades\LaraHooks;
$user = User::create($data);
LaraHooks::get('user.created', ['user' => $user]);Use @shook to define insertion points where content can be injected:
<div class="header">
<h1>Welcome</h1>
@shook('header.notifications')
</div>
<div class="sidebar">
@shook('sidebar.widgets')
</div>
Inject content at these points:
LaraHooks::listen('header.notifications', function ($callback, $output, $params) {
return '<div class="notification">New messages!</div>';
}, 10);
LaraHooks::listen('sidebar.widgets', function ($callback, $output, $params) {
return '<div class="weather-widget">Weather Widget</div>';
}, 10);Use @hook and @endhook to wrap content that can be modified or replaced:
@hook('product.card')
<div class="product">
<h3>{{ $product->name }}</h3>
<p>{{ $product->price }}</p>
</div>
@endhook
@hook('footer.content')
<div class="default-footer">
<p>© 2024 Company Name</p>
</div>
@endhook
Modify or replace the wrapped content:
LaraHooks::listen('product.card', function ($callback, $output, $params) {
// Wrap existing content with additional markup
return '<div class="featured-badge">Featured</div>' . $output;
}, 10);
LaraHooks::listen('footer.content', function ($callback, $output, $params) {
// Replace entire content
return '<div class="custom-footer"><p>Custom Footer Content</p></div>';
}, 10);Lower numbers execute first:
LaraHooks::listen('user.login', function () {
// Runs first
}, 1);
LaraHooks::listen('user.login', function () {
// Runs second
}, 10);
LaraHooks::listen('user.login', function () {
// Runs third
}, 100);Insertion hooks (@shook) append listener results by default, including when
multiple listeners use different or the same priority. To replace all listeners
currently registered for a hook and replace the output, pass true as the fourth
argument:
LaraHooks::listen('user.login', function () {
// Replaces all previously registered user.login listeners
}, 10, true);LaraHooks::listen('payment.process', function ($callback, $output, $params) {
if ($params['amount'] > 1000) {
LaraHooks::stop('payment.process');
return 'Payment requires manual review';
}
}, 1);
LaraHooks::listen('payment.process', function ($callback, $output, $params) {
// This will not run if the amount > 1000
return 'Payment processed successfully';
}, 10);Provide a fallback when no hooks are registered:
$content = LaraHooks::get('custom.content', [], function () {
return 'Default content when no hooks registered';
});LaraHooks::get('order.created', [
'order' => $order,
'user' => $user,
'total' => $total
]);Access parameters in listeners:
LaraHooks::listen('order.created', function ($callback, $output, $params) {
$order = $params['order'];
$user = $params['user'];
$total = $params['total'];
// Process order...
}, 10);You can combine both directives for maximum flexibility:
<div class="page-content">
@shook('content.before')
@hook('main.content')
<article>
<h1>{{ $title }}</h1>
<p>{{ $content }}</p>
</article>
@endhook
@shook('content.after')
</div>
Register listeners:
// Insert content before main content
LaraHooks::listen('content.before', function ($callback, $output, $params) {
return '<div class="breadcrumbs">Home > Article</div>';
}, 10);
// Wrap main content
LaraHooks::listen('main.content', function ($callback, $output, $params) {
return '<div class="content-wrapper">' . $output . '</div>';
}, 10);
// Insert content after main content
LaraHooks::listen('content.after', function ($callback, $output, $params) {
return '<div class="related-articles">Related Articles</div>';
}, 10);Mock hooks in your tests:
use RealZone22\LaraHooks\Facades\LaraHooks;
public function test_user_creation_with_hooks(): void
{
LaraHooks::mock('user.created', 'mocked response');
$result = LaraHooks::get('user.created', ['user' => $user]);
$this->assertEquals('mocked response', $result);
}Run the test suite:
composer testList all registered hooks and their locations:
php artisan hook:listThis command shows:
- All registered hook listeners with priorities
- Hooks found in Blade templates (
@hook/@endhook,@shook) - Hooks called in PHP classes (
LaraHooks::get())
// In a plugin's service provider
LaraHooks::listen('admin.menu', function ($callback, $output, $params) {
return $output . '<li><a href="/plugin">My Plugin</a></li>';
}, 50);{{-- Base theme template --}}
@hook('footer.widgets')
<div class="default-footer-widgets">
<div class="widget">Default Widget</div>
</div>
@endhook
// In child theme service provider
LaraHooks::listen('footer.widgets', function ($callback, $output, $params) {
return '<div class="custom-footer-widgets">Custom Widgets</div>';
}, 10);{{-- Template with injection point --}}
<div class="dashboard">
<h1>Dashboard</h1>
@shook('dashboard.widgets')
</div>
// Multiple plugins can inject widgets
LaraHooks::listen('dashboard.widgets', function ($callback, $output, $params) {
return '<div class="analytics-widget">Analytics</div>';
}, 10);
LaraHooks::listen('dashboard.widgets', function ($callback, $output, $params) {
return $output . '<div class="stats-widget">Statistics</div>';
}, 20);LaraHooks::listen('invoice.paid', function ($callback, $output, $params) {
// Update accounting system
}, 10);
LaraHooks::listen('invoice.paid', function ($callback, $output, $params) {
// Send receipt email
}, 20);
LaraHooks::listen('invoice.paid', function ($callback, $output, $params) {
// Update analytics
}, 30);// Register a listener (appended by default)
LaraHooks::listen(string $hook, callable $callback, int $priority = 10, bool $replace = false): void
// Execute a hook
LaraHooks::get(string $hook, array $params = [], ?callable $callback = null, string $htmlContent = ''): mixed
// Stop hook execution
LaraHooks::stop(string $hook): void
// Mock hook for testing
LaraHooks::mock(string $hook, mixed $return): void
// Get all registered hooks
LaraHooks::getHooks(): array
// Get all listeners
LaraHooks::getListeners(): array
// Get listeners for specific hook
LaraHooks::getEvents(string $hook): array{{-- Insertion point - content will be injected here --}}
@shook('hook.name')
{{-- Content wrapper - content inside can be modified or replaced --}}
@hook('hook.name')
Content that can be modified or replaced
@endhook
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.