Skip to content

Renderers

Renderers turn a collector’s results into a string. Use one when callback results need to become text, such as a list of labels or a short piece of markup. Renderers apply to collectors only.

Configure a renderer with setRenderer() and call render() when the caller needs formatted output.

The renderer methods have distinct responsibilities:

Method Use it for Returns or does
setRenderer(string $hookPoint, Renderer|callable|string $renderer) Choosing the formatter for a collector point. A class name is resolved when rendering runs. The same Hooks instance. The renderer occupies the collector’s processing slot.
render(string $hookPoint, mixed ...$arguments) Collecting results and passing them with ProcessingContext to the configured renderer. A string; throws MissingRendererException when unset and InvalidRendererException for invalid output or configuration.
renderWith(string $hookPoint, Renderer|callable|string $renderer, mixed ...$arguments) Applying a renderer for one call without configuring the hook point. A string; does not read or change the persistent processor slot.

Renderers apply only to collectors. Use collect() for raw results and process() for non-string reductions.

render() collects one raw result per listener, passes the list and a ProcessingContext to the renderer, and requires a string result. For example, an order receipt can collect structured sections and leave the final layout to one renderer:

use App\Models\Order;
use Magdicom\Hooks;
$hooks = new Hooks();
$hooks->addCollector('order.receipt.sections', static fn (Order $order): array => [
'title' => 'Payment',
'rows' => [
['label' => 'Method', 'value' => $order->paymentMethod],
['label' => 'Total', 'value' => $order->formattedTotal()],
],
]);
$hooks->setRenderer('order.receipt.sections', OrderReceiptRenderer::class);
$html = $hooks->render('order.receipt.sections', $order);

OrderReceiptRenderer is an application class implementing Renderer. It receives the raw section list and a ProcessingContext, escapes values, and returns the final HTML. Keeping that work in one place means extensions contribute data without controlling the receipt layout. See the order receipt use case for the complete renderer example.

collect() remains the raw operation even when a renderer is configured. process() and render() use the same collector result slot, so configure the endpoint for the operation your caller needs.

Without a configured renderer, render() throws MissingRendererException. A class-name renderer must resolve to Renderer; an invalid implementation raises InvalidRendererException. A callable renderer that returns a non-string raises the same exception.

Use renderWith() when the output format is a choice made by the current call rather than a permanent setting for the collector. It collects the raw results once, passes them with ProcessingContext to the supplied renderer, and requires a string result. The renderer can be an instance, callable, or resolver-backed class name. It does not replace an existing renderer or processor configured with setRenderer() or setProcessor().

For example, the same collected labels can use a separator selected by the current presentation:

use Magdicom\Hooks;
use Magdicom\Processors\ConcatenateRenderer;
$hooks = new Hooks();
$hooks->addCollector('navigation.labels', static fn (): string => 'Hooks');
$hooks->addCollector('navigation.labels', static fn (): string => 'Billing');
$label = $hooks->renderWith(
'navigation.labels',
new ConcatenateRenderer(' / '),
);
// 'Hooks / Billing'

renderWith() is part of magdicom/hooks v2.0.0-beta.2. Use it when one call needs a different renderer without changing the persistent renderer configured for that hook point.

The built-in Magdicom\Processors\ConcatenateRenderer joins rendered entries with a separator:

final class ConcatenateRenderer implements Renderer
{
public function __construct(string $separator = '')
}

It accepts null (as an empty string), strings, scalar values, and Stringable objects. An unsupported value throws UnexpectedValueException. An empty result list renders as an empty string.

<?php
declare(strict_types=1);
use Magdicom\Hooks;
use Magdicom\Processors\ConcatenateRenderer;
$hooks = new Hooks();
$hooks->addCollector('navigation.labels', static fn (): string => 'Hooks');
$hooks->addCollector('navigation.labels', static fn (): string => 'Beta');
$hooks->setRenderer('navigation.labels', new ConcatenateRenderer(' · '));
$label = $hooks->render('navigation.labels');
// 'Hooks · Beta'

A callable renderer receives (array $results, ProcessingContext $context) and must return a string:

<?php
declare(strict_types=1);
use Magdicom\Hooks;
use Magdicom\ProcessingContext;
$hooks = new Hooks();
$hooks->addCollector('report.sections', static fn (): string => 'Introduction');
$hooks->setRenderer('report.sections', static function (array $results, ProcessingContext $context): string {
return implode(' / ', $results);
});
$heading = $hooks->render('report.sections');

A class-name renderer is resolved through the configured core Resolver and must implement Renderer. new Hooks() uses native new $className() resolution. Inject a custom resolver with new Hooks($resolver); the Laravel wrapper supplies its container-backed resolver.

Renderers do not apply to actions or filters. Use processors for non-string reductions and collectors for unprocessed result lists.