hoa/dispatcher
Hoa\Dispatcher is a PHP component for routing and dispatching requests to controllers, offering a flexible foundation for MVC-style applications. It integrates with Hoa\Router and supports customizable dispatch rules and controller resolution.
Know what this is before you composer require it. hoa/dispatcher is not an event dispatcher and has nothing to do with Illuminate\Contracts\Events\Dispatcher. It is the second half of the Hoa MVC stack: Hoa\Router matches a request and yields a (controller, action, variables) tuple; Hoa\Dispatcher turns that tuple into an actual method call. The closest Laravel analogue is Illuminate\Routing\ControllerDispatcher (Route::get('/x', [Foo::class, 'bar'])). If you were hoping for Event::listen(), close the tab.
Install and pin.
composer require hoa/dispatcher:^3.16
This drags in hoa/router, hoa/consistency, hoa/zformat, and hoa/protocol. The last release is 2017-05-02 — pin the exact version, and expect no further ones.
Spike before you commit. Run this fifteen-minute check on your target runtime:
php -v # if 8.x, keep reading the Gotchas section first
php -r 'require "vendor/autoload.php"; var_dump(class_exists("Hoa\\Dispatcher\\Basic"));'
Hoa libraries of this vintage frequently break on PHP 8 because of $errcontext parameters in set_error_handler callbacks (removed in PHP 8). Find out in a scratch directory, not in app/.
Where to look first. The whole package is roughly one class plus a value object:
vendor/hoa/dispatcher/Basic.php — dispatch(), getController(), getAction(), getVariables().vendor/hoa/dispatcher/Kit.php — the bag of context passed between dispatcher and controller.vendor/hoa/dispatcher/ClassMethod.php — the thinner variant if Basic does too much.Read all three in one sitting; it is a few hundred lines of readable PHP and you will understand the package completely.
First use case. A standalone script or an Artisan command where you already have a route table and want controller resolution without booting Laravel's HTTP kernel:
$router = new Hoa\Router\Http();
$router->get('hello', '/hello/(?<name>\w+)', 'App\Main', 'hello');
try {
$router->route();
} catch (Hoa\Router\Exception\NotFound $e) {
// 404 path
}
$dispatcher = new Hoa\Dispatcher\Basic();
$kit = $dispatcher->dispatch($router);
The Laravel equivalent you are replacing:
Route::get('/hello/{name}', [App\Main::class, 'hello']);
If your first use case is "inside a Laravel HTTP request", stop — you already have a better dispatcher.
1. The honest pattern: use it as a reference, not a dependency. Basic::dispatch() is a service-locator-free string-to-callable resolver. The ~150 lines that actually matter are worth reading and reimplementing against Laravel's container rather than carrying a 2017 dependency. If you need the same shape, this is the whole idea:
$callable = app()->make($controller);
return $callable->{$action}(...array_values($variables));
That is genuinely most of what Hoa\Dispatcher\Basic does, minus the zformat configuration layer.
2. Standalone sub-app inside a Laravel project. If you must keep the dependency (e.g. porting a legacy Hoa module), isolate it behind a service provider and call it from one place:
// app/Providers/LegacyDispatcherServiceProvider.php
public function register(): void
{
$this->app->singleton(\Hoa\Dispatcher\Basic::class, fn () => new \Hoa\Dispatcher\Basic([
'controller' => 'App\\Http\\Legacy\\\\(:controller)',
'action' => '(:action)Action',
]));
}
Then invoke from a route closure or command — never let Hoa's router own your HTTP entry point, or you lose middleware, CSRF, session, and validation.
3. Handler-map dispatch via the router's tuple. The one transferable idea with real day-to-day value: treat (controller, action) as data and keep an explicit map instead of magic. In Laravel this beats both Route:: spaghetti and app($class) string calls scattered around:
// config/handlers.php
return [
'user.created' => [App\Handlers\UserHandler::class, 'onCreated'],
];
[$class, $method] = config("handlers.{$eventName}");
app()->call([app($class), $method], $payload);
app()->call() gives you named injection and container resolution — everything Hoa\Dispatcher cannot do.
4. Variables in, positional out. Hoa\Dispatcher passes route variables as an ordered array; actions receive them positionally. There is no name-based injection, no type-hint resolution, no route-model binding. Laravel's RouteDependencyResolverTrait does all of that. If you replicate the look of the Hoa API in your own code, still delegate to app()->call() so you keep Laravel's resolution semantics.
5. Kit-based context. Hoa\Dispatcher\Kit carries router, dispatcher, data, view, controller, action, variables through the call chain — a poor man's request container. In Laravel, Request, the container, and View fill these roles natively. Do not try to mirror Kit in Laravel code; you will end up with an object nobody can trace.
6. No middleware, no pipeline. There is nothing to hook. If you need cross-cutting concerns around a Hoa dispatch, wrap the call in a Laravel middleware or a decorator. Bridging Illuminate\Pipeline into Hoa\Router is not worth the impedance mismatch.
7. Testing. Basic is trivially testable precisely because it has no container: pass a configuration array and a stub Hoa\Router\Router whose getTheRule() returns a fixed tuple. If you are evaluating whether to port the logic, write those two tests first — it takes ten minutes and tells you whether the abstraction earns its keep.
8. Extension surface. Subclass Hoa\Dispatcher\Basic and override getController(), getAction(), or getVariables(); set $_kitClass to swap the context object. That is the entire customization model. It is a fork-friendly package, which is another way of saying it is unmaintained.
$errcontext, removed in PHP 8.0. If Hoa\Consistency fails to bootstrap on your runtime, the cost of patching a vendored dependency usually exceeds the value of the package. Check vendor/hoa/consistency/Consistency.php before anything else.hoa/consistency can register its own autoloader and, in some versions, its own error and shutdown handlers. Inside a Laravel app that can hijack Ignition/Whoops error pages. Grep for set_error_handler, register_shutdown_function, and spl_autoload_register in vendor/hoa/consistency/ and audit what gets registered on boot.Hoa\Zformat is its own mini-language. Configuration strings like 'App\\Http\\Legacy\\\\(:controller)' and '(:action)Action' use placeholder syntax with non-obvious backslash escaping for namespaced class names. When a controller "cannot be found," dump the resolved configuration from the dispatcher before touching your class names — nine times in ten the escaping is wrong, not the class.dispatch() without $router->route() first gives you a null or unset rule and an opaque Hoa\Router exception. Always try { $router->route(); } catch (Hoa\Router\Exception\NotFound $e) { ... } before dispatching.Basic may catch throwables and forward to an error action instead of propagating. If you need Laravel's Handler to see failures, wrap the dispatch() call in try/catch, normalize, and rethrow.Hoa\Exception subclasses print hoa://-style traces that look nothing like Laravel's log format and will confuse anyone triaging Sentry. Normalize the message and drop the Hoa trace before it reaches your logger.var_dump($router->getTheRule()) → confirm the tuple → var_dump($dispatcher) → confirm the resolved controller/action strings → then look at your class. Almost every "dispatch failed" report is a mismatch in that first tuple, not a bug in the package.How can I help you explore Laravel packages today?