Plugin Improvements - markstory/cakephp GitHub Wiki
Problem
Plugins have no central/single place to define their behavior around bootstrapping, routes and CLI commands. We rely on plugin users to copy the correct Plugin::load()
call from plugin README files resulting in an error prone process.
With the creation of the Application
classes the differences between applications and plugins has grown more visible. Plugins initialization has no easy way to leverage command classes, and aliased commands. Furthermore, it will be very difficult to amend plugins to use a dependency container in the future as plugins are created from snippet files and global statics.
Goals of the Solution
The new plugin initialization should aim to solve the above problems, and have the following properties:
- Should work well with the
Application
class and be compatible with DI containers defined in the application, or by CakePHP in the future. - Should avoid adding new global static features.
- Backwards compatibility with
Plugin
methods. This 'facade' class is a useful shortcut for accessing which plugins are loaded. - Enable loading plugins with a single method call.
- Allow plugins to load configuration, routes, add console commands, and middleware automatically.
- Allow application developers to disable all of the above and handle those resource types manually.
Plugin App Classes
Encapsulating the default behavior of a plugin can be supported by creating
plugin 'application' classes. Plugin app classes can be added to the host
application via a inside its bootstrap()
hook.
Plugin classes can define the following hook methods:
bootstrap()
Load plugin configuration, constants and global event listeners. Called after application bootstrapping is complete.console(CommandCollection $commands)
Add console commands for the plugin. Called after application commands are added to the CommandCollection.middleware(MiddlewareStack $middleware)
Add middleware for the plugin. Called after the application middleware hook is called.routes(RouterBuilder $routes)
Define routes for the plugin. Called after the application routes have been defined.
By implementing any of these hook methods, a plugin will have them automatically invoked by the application at the appropriate time.
Loading Plugins
Plugins are added into the application during the application bootstrap()
. You
can load plugins by object, or classname:
// In Application::bootstrap()
// By classname - Accepts all defaults.
$this->addPlugin(ExamplePlugin::class)
// By object, allows customization
$plugin = new OtherPlugin($this);
$this->addPlugin($plugin)
Once loaded application plugins are inserted into Cake\Core\Plugin
for
backwards compatibility. They are also added to $application->plugins
which is
a collection object similar Cake\Console\CommandCollection
providing accessor
methods and an iterator. It will also support fetching plugins that match
given method support. For example plugins with routing enabled:
// In the scope of an application object
foreach ($this->plugins->with('routes') as $plugin) {
$plugin->routes($routeBuilder);
}
Taking Control of Plugin Setup
The PluginApp
base class provides a suite of configuration methods that allow
the application to disable specific hooks on a plugin as needed:
$plugin = new ExamplePlugin($application);
// Disable hooks
$plugin->disableRoutes();
$plugin->disableMiddleware();
$plugin->disableConsole();
$plugin->disableBootstrap();
$application->addPlugin($plugin);
Plugins also accept an array of options in their constructor to disable hooks:
$plugin = new ExamplePlugin($this, [
'routes' => false,
'middleware' => false,
'console' => false
]);
Customizing Plugin Routes
Sometimes applications need to apply additional routing scopes around plugin
routes, or place them higher in the routes file. After disabling automatic route
loading with disableRoutes()
a plugin's routes can be loaded in the
application routes.
// In Application::routes() or config/routes.php
$routes->scope('/plugs', function ($routes) {
$routes->loadPlugin('Example');
});
This leverages the existing plugin route loading method on RouteBuilder
, and
the static Plugin
class to gain access to the correct plugin and invoke it's
routes hook. In 4.x the RouteBuilder
can be provided the collection of loaded
plugins.