Sign up to our newsletter to receive updates on Winter CMS releases,
new features in the works, and much more.
We'll never spam or give
this address away.
Plugin replacement is a feature that allows you to create a plugin that replaces (or overrides) another plugin. This is useful when you're forking a plugin to add your own functionality but want to be able to seamlessly migrate from and act as a drop in replacement for the original plugin (i.e. retaining original data, fulfilling other plugin's dependencies on the original plugin, etc).
To enable the plugin replacement feature, specify the identifier for the plugin that your plugin is replacing in the replaces
key within the pluginDetails
array, along with the version constraints that define what versions of the plugin are able to be replaced by your plugin.
public function pluginDetails()
{
return [
'name' => 'Acme Plugin',
'replaces' => [
'Acme.Original' => '>=5.0 <=6.0.4'
],
];
}
Version constraints allow you to restrict your plugin to only override currently installed plugins of specific versions. The above example showcases only replacing a plugin if the original plugin is any version between 5.0
and 6.0.4
inclusive. Most of the time the actual version constraint you'll use will be much simpler, a simple <2.0
to indicate all versions immediately prior to the version you first release your replacement plugin as.
This means you don't have to worry about new versions of the original plugin having changes that may conflict with your changes to the plugin.
Version constraints are specified in the same format that Composer uses. Some valid examples would be:
1.0
>=1.0.3
<2.0
>=1.5.0 <2.0.0
self.version
By specifying a version, your plugin will check what version the original plugin is installed at and only if it's version matches the constraint will it disable the original and enable the replacement. If this match fails, then the replacement will be disabled and the original plugin will stay enabled.
NOTE: This is for reference only. By registering a plugin as a replacement in the Plugin registration file, Winter automatically handles registering these aliases for you.
Aliasing is a feature of Winter that allows for backwards compatibility and support for inheriting replaced plugins:
This will allow, for example, plugins that use the original plugin functionality to still work even if the original class names are used. If a plugin replaces an original plugin that is a dependency of a third plugin, aliasing will resolve the dependency for the third plugin, allowing the third plugin to continue to function.
Configuration supports 2 different types of aliasing: registerNamespaceAlias
& registerPackageFallback
.
This method allows for redirection of the alias to the namespace while accessing config values.
Config::registerNamespaceAlias('winter.replacement', 'winter.original');
For example, register the following config as plugins/winter/replacement/config/config.php
:
<?php
return [
'foo' => 'bar'
];
The config will be accessible via the alias registered:
config('winter.original::foo'); // returns bar
This method allows falling back to an aliased global config (a config specified in /config/acme/plugin/config.php
).
Config::registerPackageFallback('winter.replacement', 'winter.original');
The logic to this is as follows:
/config/winter/replacement/config.php
exists it will be registered under the winter.replacement
namespace./config/winter/replacement/config.php
does not exist, it will check /config/winter/original/config.php
and if found,
it will be registered under the winter.replacement
.Allows for redirection of calls to the alias and returns values from the namespace.
Lang::registerNamespaceAlias('winter.replacement', 'winter.original');
For example, register the following config as plugins/winter/replacement/lang/en/lang.php
:
<?php
return [
'foo' => 'bar'
];
The translation will be accessible via the alias registered:
Lang::get('winter.original::foo'); // returns bar
There are 2 methods for registering settings aliases. Firstly the aliases can be registered prior to the PluginManager
init via lazyRegisterOwnerAlias
.
SettingsManager::lazyRegisterOwnerAlias('Winter.Replacement', 'Winter.Original');
If the PluginManager
has been loaded, then aliases can be registered via:
SettingsManager::instance()->registerOwnerAlias('Winter.Replacement', 'Winter.Original');
There are 2 methods for registering settings aliases. Firstly the aliases can be registered prior to the PluginManager
init via lazyRegisterOwnerAlias
.
NavigationManager::lazyRegisterOwnerAlias('Winter.Replacement', 'Winter.Original');
If the PluginManager
has been loaded, then aliases can be registered via:
NavigationManager::instance()->registerOwnerAlias('Winter.Replacement', 'Winter.Original');
When forking a plugin and using the replace functionality, you will need to handle migrating the data from the original plugin to your replacing plugin via migrations, seeders and the model classes. To do this we recommend the following:
An example migration could look something like this:
<?php namespace Winter\Plugin\Updates;
use Schema;
use Winter\Storm\Database\Updates\Migration;
class RenameTables extends Migration
{
const TABLES = [
'example',
'foo',
'bar'
];
public function up()
{
foreach (self::TABLES as $table) {
$from = 'acme_plugin_' . $table;
$to = 'winter_plugin_' . $table;
if (Schema::hasTable($from) && !Schema::hasTable($to)) {
Schema::rename($from, $to);
}
}
}
public function down()
{
foreach (self::TABLES as $table) {
$from = 'winter_plugin_' . $table;
$to = 'acme_plugin_' . $table;
if (Schema::hasTable($from) && !Schema::hasTable($to)) {
Schema::rename($from, $to);
}
}
}
}
If an old migration (i.e. any migration that runs before the migration that renames the tables) is using a model to populate data, it will be referencing the new table and that will cause issues while updating. The solution to this is dynamically renaming the table before inserting/modifying data:
ExampleModel::extend(function ($model) {
$model->setTable('acme_plugin_example');
});
// execute seeding code
ExampleModel::extend(function ($model) {
$model->setTable('winter_plugin_example');
});
If the models use the unique
validation rule, you should make sure that the rule is implemented without any modifiers (i.e. just 'slug' => 'unique'
, not 'slug' => 'unique:winter_plugin_table'
so that the call to $model->setTable()
will also take effect in that validation logic.
Sign up to our newsletter to receive updates on Winter CMS releases,
new features in the works, and much more.
We'll never spam or give
this address away.
Published June 3, 2023
The Winter CMS maintainers are pleased to announce that Winter CMS v1.2.2 is now available, including child themes support, a new Icon Finder widget, various improvements to the Media Manager and much more.
Released May 31, 2023
13 UX/UI Improvements, 22 API Changes, 19 Bug Fixes, 0 Security Improvements
-, 6 Translation Improvements, 1 Performance Improvement, 3 Community Improvements, 3 Dependencies, 0 New Contributors
* @Teranode made their first contribution in https://github.com/wintercms/winter/pull/806
* @wpjscc made their first contribution in https://github.com/wintercms/winter/pull/841
* @xitara made their first contribution in https://github.com/wintercms/winter/pull/858
* @webbati made their first contribution in https://github.com/wintercms/winter/pull/869
* @zaxbux made their first contribution in https://github.com/wintercms/winter/pull/890
* @djpa3k made their first contribution in https://github.com/wintercms/winter/pull/911