thunderer/shortcode
Framework-agnostic PHP library for parsing and processing shortcodes/BBCodes. Extract shortcodes from text, handle replacements, and apply them via processors and events. Includes serializers for Text/XML/JSON/YAML. Supports PHP 5.3–8.x.
Installation:
composer require thunderer/shortcode
Add to composer.json if not using Composer directly.
Basic Usage:
use Thunder\Shortcode\ShortcodeFacade;
$facade = new ShortcodeFacade();
$facade->addHandler('greet', fn($s) => "Hello, {$s->getParameter('name')}!");
echo $facade->process('[greet name="John"]');
// Output: Hello, John!
First Use Case: Replace dynamic content in a Laravel Blade template:
// In a service provider or controller
$facade = new ShortcodeFacade();
$facade->addHandler('user', fn($s) => auth()->user()->{$s->getParameter('field')});
// In Blade: {{ $facade->process('[user field="name"]') }}
Handler Registration:
// Single handler
$facade->addHandler('alert', fn($s) =>
"<div class='alert'>" . $s->getContent() . "</div>"
);
// Multiple handlers (e.g., in a service provider)
$handlers = [
'alert' => fn($s) => "<div class='alert'>" . $s->getContent() . "</div>",
'button' => fn($s) => "<button>{$s->getContent()}</button>",
];
foreach ($handlers as $name => $callback) {
$facade->addHandler($name, $callback);
}
Nested Shortcodes:
$facade->addHandler('container', fn($s) =>
"<div>" . $facade->process($s->getContent()) . "</div>"
);
echo $facade->process('[container][alert]Error![/alert][/container]');
Dynamic Handlers (Laravel Example):
// Register handlers based on database config
$shortcodeConfigs = ShortcodeConfig::all();
foreach ($shortcodeConfigs as $config) {
$facade->addHandler($config->name, function($s) use ($config) {
return app($config->handler_class)->render($s);
});
}
Blade Integration:
// Create a Blade directive
Blade::directive('shortcode', function ($expression) {
return "<?php echo app('shortcode')->process({$expression}); ?>";
});
// Usage in Blade: @shortcode('[user field="email"]')
Event-Driven Processing:
$facade->addEventHandler(\Thunder\Shortcode\Events::FILTER_SHORTCODES, function($event) {
if ($event->getParent()->getName() === 'raw') {
$event->setShortcodes([]); // Skip nested shortcodes
}
});
Custom Parsers:
$customSyntax = (new \Thunder\Shortcode\Syntax\SyntaxBuilder())
->setOpeningTag('{')
->setClosingTag('}')
->getSyntax();
$facade->setParser(new \Thunder\Shortcode\Parser\RegularParser($customSyntax));
Serialization:
$shortcode = $facade->parse('[alert]Warning![/alert]');
$json = $facade->serialize($shortcode, 'json');
$restored = $facade->unserialize($json, 'json');
Configuration Management:
// In config/shortcodes.php
return [
'recursion_depth' => 3,
'max_iterations' => 2,
];
// Apply in service provider
$facade->setProcessor(
$facade->getProcessor()
->withRecursionDepth(config('shortcodes.recursion_depth'))
);
Recursion Depth:
null allows unlimited nesting, which can cause stack overflows.withRecursionDepth(3) for most use cases to prevent infinite loops.Auto-Processing Content:
withAutoProcessContent(true) (default) processes nested shortcodes automatically.false if you need manual control over nested processing.Parser Quirks:
RegexParser fails with overlapping shortcodes (e.g., [a][a/]).RegularParser for complex nested structures.Parameter Handling:
[tag param=value]) may break if values contain spaces.[tag param="value with spaces"].Case Sensitivity:
strtolower()) if case-insensitive behavior is needed.Inspect Parsed Shortcodes:
$shortcodes = $facade->parse('[alert]Warning![/alert]');
dd($shortcodes); // Debug parsed structure
Log Handlers:
$facade->addHandler('debug', function($s) {
\Log::debug('Shortcode:', [
'name' => $s->getName(),
'params' => $s->getParameters(),
'content' => $s->getContent(),
]);
return $s->getContent();
});
Test Edge Cases:
// Test unclosed shortcodes
$facade->process('[alert]Warning!'); // Should output raw
// Test invalid syntax
$facade->process('[alert param=value]'); // Ensure proper escaping
Custom Handlers:
// Dynamic handler resolution (e.g., from a database)
$facade->addHandler('dynamic', function($s) {
$handler = app("ShortcodeHandler:{$s->getParameter('type')}");
return $handler->render($s);
});
Plugin System:
// Register shortcodes via plugins
event('shortcode.register', function($facade) {
$facade->addHandler('plugin-tag', fn($s) => "Plugin: {$s->getContent()}");
});
Caching Handlers:
// Cache handler results (e.g., for expensive operations)
$facade->addHandler('expensive', function($s) {
return cache()->remember("shortcode:{$s->getName()}", now()->addHours(1), function() {
return app('ExpensiveService')->process($s);
});
});
Laravel Service Provider:
// Bind facade to Laravel container
$this->app->singleton('shortcode', function($app) {
$facade = new ShortcodeFacade();
// Register default handlers here
return $facade;
});
Middleware for Shortcodes:
// Process shortcodes in HTTP responses
$response->setContent(
$facade->process($response->getContent())
);
How can I help you explore Laravel packages today?