Plugins¶
A plugin is an ordinary Composer package that declares "type": "naf-plugin". That type
is the whole discovery mechanism: the framework asks Composer which installed packages have
it and boots them. There is nothing to register and no list to maintain.
It also means the type has to be right. A package that installs correctly but declares a
different type is not loaded — silently, without an error — which is exactly what happens
to a nixphp-plugin under NAF 0.2.
Plugin Structure¶
A NAF plugin mimics the structure of a full app:
your-plugin/
├── app/
│ ├── config.php // Plugin-specific configuration
│ └── views/ // Plugin-specific templates
│ └── example.phtml
├── bootstrap.php // Bootstrap logic (routes, events, services, etc.)
└── composer.json
app/config.phpis merged into the global config.app/views/is added to the view resolver.bootstrap.phpis automatically executed when the plugin is discovered.
Example composer.json¶
Below is a minimal but complete composer.json for a NAF plugin:
{
"name": "vendor/naf-plugin-example",
"description": "Skeleton for your first plugin when using NAF",
"type": "naf-plugin",
"license": "MIT",
"authors": [
{
"name": "Your Name",
"email": "your@mail.com"
}
],
"require": {
"php": ">=8.3",
"naf/framework": "^0.2"
},
"autoload": {
"psr-4": {
"MyPlugin\\": "app/"
}
},
"minimum-stability": "stable",
"prefer-stable": true
}
✅ Important: -
"type": "naf-plugin"is required for discovery. - The namespace (e.g.MyPlugin\\) must match your plugin classes location.
Run:
composer dump-autoload
To ensure your classes are properly registered.
Automatic Discovery¶
Plugins are discovered via Composer using the package "type": "naf-plugin". Once installed, NAF will:
- Load
bootstrap.php - Merge
app/config.php - Register all
app/views/templates
No manual registration is needed.
Accessing Plugin Metadata¶
use function Naf\plugin;
$plugins = plugin(); // array<string, Naf\Support\Plugin>
$view = plugin('naf/view'); // Plugin; ask only for an installed package
$view->getViewPaths();
$view->getConfigPaths();
$view->getBootstrapFile();
For internal use or debugging only – no need to register anything yourself.
Example Plugin: Event Listener¶
my-event-plugin/
├── app/
│ └── Listeners/
│ └── UserListener.php
└── bootstrap.php
namespace MyEventPlugin\Listeners;
use function Naf\log;
class UserListener
{
public function onUserRegistered($user)
{
// Log registration, send welcome email, etc.
log()->info('New user registered: ' . $user->email);
}
}
use MyEventPlugin\Listeners\UserListener;
use function Naf\{event, log};
// Register the event listener
$listener = new UserListener();
event()->listen('user.registered', [$listener, 'onUserRegistered']);
// You can also use closure-based listeners
event()->listen('user.login', function($user) {
log()->info('User logged in: ' . $user->email);
});
use function Naf\{event, log};
// After successful user registration:
$user = new User(); // Your user object
event()->dispatch('user.registered', $user);
Example Plugin: Routing to a Controller¶
Structure:
my-hello-plugin/
├── app/
│ └── Controllers/
│ └── HelloController.php
├── bootstrap.php
└── composer.json
HelloController.php:
namespace MyHelloPlugin\Controllers;
use Psr\Http\Message\ResponseInterface;
use function Naf\response;
class HelloController
{
public function index(): ResponseInterface
{
return response('Hello from the plugin controller!');
}
}
bootstrap.php:
use MyHelloPlugin\Controllers\HelloController;
use function Naf\route;
route()->add('GET', '/plugin-hello', [HelloController::class, 'index'], 'plugin.hello');
Visit: http://yourapp.local/plugin-hello
View Resolution Order¶
- App
app/views/ - Plugins
app/views/
Config Merge Order¶
- Framework defaults
- Plugin configuration, in plugin order
- Application configuration (
app/config.php, orsrc/config.phpas a fallback)
Later values override earlier values recursively. See Configuration.
Boot order¶
All discovered plugins are registered before any bootstrap runs. hasPlugin() says that a
plugin is registered; it does not say that its bootstrap has already run. Prefer lazy service
factories so a dependency is resolved after registration has finished.
When boot order matters, create app/plugins.php returning the package names to load first:
<?php
return ['naf/session', 'naf/form'];
Other installed plugins follow. This orders installed plugins; it does not install packages or
act as an allow-list. Application routes load after plugin bootstraps. Application service
bindings belong in the root bootstrap.php, after the autoloader and before app()->run().
The loader also accepts src/config.php, src/routes.php and src/functions.php when their
app/ counterparts are absent. Keep one layout per package to avoid competing files.