英文:
How properly update plugins in Shopware 6?
问题
以下是翻译好的部分:
显然,自动化插件更新存在很大的限制。Shopware 6提供两种自动更新插件的方式:“生命周期方法” 和 “迁移”。不幸的是,这两种方式都不够。 😭😭😭 这可能是我做错或遗漏了某些东西。
假设我想安装来自第三方的现有插件名为MyCustomPlugin
。该插件当前版本为2.9.3
。但是,该插件从1.0.0
版本开始,并且自那时以来发生了许多更新。例如:
1.0.0 => 1.0.1
1.0.1 => 1.0.2
1.0.2 => 1.0.3
....
....
2.9.1 => 2.9.2
2.9.2 => 2.9.3
当然,在这些更新中,需要自动更新以创建目录,执行计算,调用外部API,使用其他插件的现有服务,创建表,添加列等。重要的是:不仅是数据库查询,还需要文件系统操作和API调用。
🚨🚨 “迁移”的限制:正如官方文档所述,迁移仅用于管理数据库。巨大的限制在于它不支持依赖注入。迁移系统只传递“Connection”给update
和updateDestructive
方法,你只能执行SQL操作。甚至无法使用存储库服务。过于受限。参见以下链接:
🚨🚨 “插件生命周期方法”的限制:它对插件更新提供了更好的支持。你可以使用服务,可以访问$this->container->get('YourServiceHere')
,但这里出现了主要问题:当运行plugin:refresh
时,Shopware检测到一个新插件(即使它没有安装),并且Shopware立即更新表plugin.version
到其当前版本。在我们的情况下,版本是2.9.3
。这意味着,我们只是将插件添加到custom/plugins/
目录,Shopware已经说它的版本是2.9.3
。这样,我们无法在安装期间(install
方法)正确管理版本系统。记住,我们有多个从版本1.0.0
到版本2.9.3
的更新脚本?Shopware已经指出版本是2.9.3
,这将完全破坏了检测需要运行哪些更新脚本的逻辑。我们如何运行适当的更新脚本(即使我们在install
方法中手动调用它们)?没有办法。还有一个问题,即该插件多次安装,操作相互冲突,例如创建表或列。这是“迁移”中不会发生的问题,因为它会跟踪已运行的所有更新脚本,不会再次运行它们。
最终结论:不幸的是,这些都不够 😭😭 请告诉我是否有什么我遗漏的东西。
👉👉 我无法使用“迁移”,因为它没有提供执行复杂更新所需的服务。
👉👉 我无法使用“插件生命周期方法”,因为在install
方法中,它不提供正确的从/到版本,因此我无法运行适当的更新脚本。
问题是:如何在Shopware 6中正确自动化插件更新?
英文:
Apparently, there are great limitations to automate plugins' updates. Shopware 6 provides two automated ways to update plugins: "lifecycle methods" AND "migrations". Unfortunately, those have not been enough. 😭😭😭 It might be something I'm doing incorrectly or missing.
Let's say that I want to install an existing plugin from a third party named MyCustomPlugin
. This plugin is currently on version 2.9.3
. However, the plugin started on 1.0.0
and lots of updates happened since then. For example:
1.0.0 => 1.0.1
1.0.1 => 1.0.2
1.0.2 => 1.0.3
....
....
2.9.1 => 2.9.2
2.9.2 => 2.9.3
Of course, in many of these updates, automated updates were necessary to create directories, perform calculations, call external API's, use existing services from other plugins, create tables, add columns.. etc. Important: not only database queries, but filesystem operations and API calls were necessary.
🚨🚨 LIMITATIONS of "Migrations": as the official docs states, migrations was exclusively created to manage the database. The huge limitation is that it does not provide support to dependency injection. The migration system just passes the "Connection" to update
and updateDestructive
methods and everything you can do is simply perform SQL operations. You can not even use repository services. Too limited. See the following links:
🚨🚨 LIMITATIONS of "Plugin lifecycle methods": it provides a much better support to plugin updates. You can use services, you have access to $this->container->get('YourServiceHere')
, but here comes the main problem: when you run plugin:refresh
, Shopware detects a new plugin (even tho it is not installed), and Shopware instantly updates the table plugin.version
to its current version. In our case that would be 2.9.3
. That means, we just added our plugin to the custom/plugins/
directory and Shopware already says it's version 2.9.3
. That way, we can not manage properly the version system during installation (install
method). Remember we had multiple update scripts from version 1.0.0
to version 2.9.3
? Shopware is already pointing out the version as 2.9.3
, and that would completely break any logic to detect what update scripts will be necessary to run. How are we going to run the appropriate update scripts (even if we call them manually on install
method)? There is no way. Also there is a problem of having this plugin being installed several times and having the same operation conflicting against each other, like creating a table or column for instance. That's a problem that doesn't happen in "Migrations", as it tracks all update scripts that were run and don't run them again.
Bottom line: unfortunately, those are not enough 😭😭 Please, let me know if I'm missing something.
👉👉 I can not use "Migrations" because it does not provide the necessary services to perform a complex update.
👉👉 I can not use "Plugin lifecycle methods" because during ìnstall
method, it does not provide the correct from/to versions and thus I'm not able to run the appropriate update scripts.
The question is: how to proper automate plugins updates in Shopware 6?
答案1
得分: 3
关于迁移(Migrations):
迁移是经过精心设计的,不允许进一步注入服务,只应用于对数据库模式进行更改,这些更改应该可以在特定版本的插件的所有安装之间互换。如果开发人员可以访问各种服务,比如仓库(repositories),他们可能会倾向于使用迁移来持久保存诸如演示数据之类的东西,这不是迁移的设计目的(不仅适用于Shopware)。
简而言之,如果我在特定版本中安装了您的插件并运行迁移,迁移的结果应该完全相同(除了随机生成的ID)。任何可能导致不同结果的操作都是不鼓励的。
关于生命周期事件(lifecycle events):
这是您可以和应该使用公共服务的地方,例如,如果您想在安装时生成动态演示数据。如您所提到的,与迁移不同,您必须自己处理特定版本的执行。
这就是为什么UpdateContext
类具有getUpdatePluginVersion
方法,用于获取您要更新到的版本,以及getCurrentPluginVersion
方法,用于获取当前版本。使用这些信息,您基本上可以通过检查插件的当前版本并运行所有操作来构建自己的迁移处理,一直迁移到最早的版本。
在初始安装中,您显然会立即运行所有这些操作。为确保不会在多次安装时出现重复数据,您还必须确保在卸载时删除所有数据。我在多次情况下都已经这样做了,如果您在何时持久保存数据以及以何种顺序进行组织上做得很好,那么这应该不会是什么大问题。
关于PHP代码部分:
您提供的是PHP代码,不需要翻译。
英文:
About Migrations
Migrations were deliberately designed to not allow further injection of services and should only serve the purpose of making changes to the database schema that should be interchangeable between all installations of a plugin given a specific version. If you gave developers access to all kinds of services, e.g. the repositories, they may feel inclined to use migrations to persist things such as demo data and the likes, which is not what migrations were designed for (and not just in a Shopware specific sense).
So, in short, if I install your plugin in a specific version and run the migrations, the result of the migrations should be exactly the same (minus randomly generated ids). Anything that could lead to varying results is discouraged.
About lifecycle events
They're the place where you can and should use public services, e.g. if you wanted to generate dynamic demo data on installation. As you mentioned, unlike migrations, you'll have to handle version specific execution by yourself.
That's why the UpdateContext
class has the method getUpdatePluginVersion
, for the version you're updating to, and the method getCurrentPluginVersion
, for the version you're currently on. Using that information you can essentially build your own migration handling by checking the current version of the plugin and running all your operations down to the earliest version you could update from.
On the initial installation you would obviously run all of these at once. To make sure you don't end up with duplicate data on repeated installations, you'd also have to make sure you delete all the data on uninstallation. I have done this on multiple occasions and if you keep the data well organized on when it is to be persisted and in what order, then this shouldn't be too much of a problem at all.
public function update(UpdateContext $updateContext): void
{
/** @var EntityRepository $repository */
$repository = $this->container->get('some_entity.repository');
$currentVersion = $updateContext->getCurrentPluginVersion();
// persist fixtures added since the current version
if (version_compare($currentVersion, '2.0.0', '<')) {
$fixtures = file_get_contents($this->getPath() . '/Resources/fixtures/update.2.0.0.json');
$repository->upsert(json_decode($fixtures, true), $updateContext->getContext());
}
if (version_compare($currentVersion, '2.1.0', '<')) {
$fixtures = file_get_contents($this->getPath() . '/Resources/fixtures/update.2.1.0.json');
$repository->upsert(json_decode($fixtures, true), $updateContext->getContext());
}
// ...
}
public function install(InstallContext $installContext): void
{
$repository = $this->container->get('some_entity.repository');
// persist all fixtures up to the latest version
$fixtures = file_get_contents($this->getPath() . '/Resources/fixtures/update.2.0.0.json');
$repository->upsert(json_decode($fixtures, true), $installContext->getContext());
$fixtures = file_get_contents($this->getPath() . '/Resources/fixtures/update.2.1.0.json');
$repository->upsert(json_decode($fixtures, true), $installContext->getContext());
// ...
}
public function uninstall(UninstallContext $uninstallContext): void
{
$repository = $this->container->get('some_entity.repository');
// delete all the fixtures up to the latest version
$ids = file_get_contents($this->getPath() . '/Resources/fixtures/ids.json');
$repository->delete(json_decode($ids, true), $uninstallContext->getContext());
}
答案2
得分: 0
在dneustadt的回答基础上,这也可以用于预配置插件(更多是针对定制插件,不太适用于Shopware商店的通用插件):
```php
public function update(UpdateContext $updateContext): void
{
if (version_compare($updateContext->getCurrentPluginVersion(), '1.0.0', '<')) {
$configService = $this->container->get(SystemConfigService::class);
$configService->set(self::CONFIG_KEY, 'foobar_', self::CHANNEL_SOMETHING);
}
}
<details>
<summary>英文:</summary>
Adding to dneustadt's answer, this can also be used to pre-configure plugins (more in custom-made plugins, not so much for general purpose plugins for the Shopware store):
public function update(UpdateContext $updateContext): void
{
if (version_compare($updateContext->getCurrentPluginVersion(), '1.0.0', '<')) {
$configService = $this->container->get(SystemConfigService::class);
$configService->set(self::CONFIG_KEY, 'foobar_', self::CHANNEL_SOMETHING);
}
}
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论