You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
: Migrations specific to your Craft project. These often contain steps that manipulate data based on handles or other identifiers that are only relevant internally.
15
15
16
+
[Modules](module-guide.md) are treated as part of your application, and should use content migrations.
17
+
16
18
## Creating Migrations
17
19
18
20
To create a new migration, use the `migrate/create` command:
Enter `yes` at the prompt, and a new migration file will be created for you. You can find it at the file path output by the command; migration classes include a timestamp prefix with the format `mYYMMDD_HHMMSS`, like `m250923_000000`.
30
32
31
-
This file and class should _never_ be renamed after release! Doing so can cause it to run again, or out of order.
33
+
This file and class should _never_ be renamed after release!
34
+
Doing so can cause it to run again, or out of order.
35
+
Similarly, the only time it is appropriate to modify an existing migration is when it produces errors for your users.
36
+
Those changes should be published as part of a new release, and they should never result in a different schema.
32
37
33
38
::: tip
34
-
If this is a plugin migration, increase your plugin’s [schema version](craft5:craft\base\PluginTrait::$schemaVersion), so Craft knows to check for new plugin migrations after an update.
39
+
If this is a plugin migration, increase your plugin’s [schema version](#schema-version), so Craft knows to run new migrations after an update.
35
40
:::
36
41
37
42
### What Goes Inside
38
43
39
-
Migration classes contain methods: [safeUp()](<yii2:yii\db\Migration::safeUp()>) and [safeDown()](<yii2:yii\db\Migration::safeDown()>). `safeUp()` is run when your migration is _applied_, and `safeDown()` is run when your migration is _reverted_.
44
+
Migration classes must define two methods:
45
+
46
+
-[safeUp()](<yii2:yii\db\Migration::safeUp()>) — Run when the migration is _applied_.
47
+
-[safeDown()](<yii2:yii\db\Migration::safeDown()>) — Run when the migration is _reverted_.
40
48
41
49
::: tip
42
50
You can usually ignore the `safeDown()` method, as Craft doesn’t have a way to revert migrations from the control panel.
51
+
During development and testing, however, you may find that it significantly easier to [roll back](#rolling-back) a migration than drop and re-import a database.
43
52
:::
44
53
45
-
You have full access to [Craft’s API](https://docs.craftcms.com/api/v5/) from your `safeUp()` method, but plugin migrations should try to avoid calling the plugin’s own API here. As your plugin’s database schema changes over time, so will your APIs assumptions about the schema. If an old migration calls a service method that relies on database changes that haven’t been applied yet, it will result in a SQL error. So in general you should execute all SQL queries directly from your own migration class. It may feel like you’re duplicating code, but it will be more future-proof.
54
+
You have full access to [Craft’s API](https://docs.craftcms.com/api/v5/) from your `safeUp()` method, but plugin migrations should try to avoid calling the plugin’s own API here.
55
+
As your plugin’s database schema changes over time, so will your APIs assumptions about the schema.
56
+
If a migration calls a service method that relies on database changes that haven’t been applied yet, it will result in a SQL error.
57
+
In general, you should execute all SQL queries _directly from that migration class_.
58
+
It may feel like you’re duplicating code, but it will be more future-proof.
59
+
Read more about this in the [rollbacks and compatibility](#rollbacks-and-compatibility) section.
60
+
61
+
When you’ve finalized a migration, make sure its effects are reflected in the [install migration](#plugin-install-migrations), as well.
62
+
When a plugin is installed
46
63
47
64
### Manipulating Database Data
48
65
49
-
Your migration class extends <craft5:craft\db\Migration>, which provides several methods for working with the database. It’s better to use these than their <craft5:craft\db\Command> counterparts, because the migration methods are both simpler to use, and they’ll output a status message to the terminal for you.
66
+
Your migration class extends <craft5:craft\db\Migration>, which provides several methods for working with the database.
67
+
These are often more convenient than their <craft5:craft\db\Command> counterparts, and they’ll output a status message to the terminal for you.
50
68
51
69
```php
52
-
// Bad:
70
+
// Traditional command:
53
71
$this->db->createCommand()
54
72
->insert('{{%mytablename}}', $rows)
55
73
->execute();
56
74
57
-
// Good:
75
+
// Migration shortcut:
58
76
$this->insert('{{%mytablename}}', $rows);
59
77
```
60
78
61
-
<craft5:craft\helpers\MigrationHelper> provides several helpful methods as well:
79
+
<craft5:craft\helpers\MigrationHelper> provides several helpful methods, as well:
62
80
63
-
-[dropForeignKeyIfExists()](craft5:craft\helpers\MigrationHelper::dropForeignKeyIfExists()) removes a foreign key if it exists, without needing to know its exact name.
64
-
-[dropIndexIfExists()](craft5:craft\helpers\MigrationHelper::dropIndexIfExists()) removes an index if it exists, without needing to know its exact name.
81
+
-[dropForeignKeyIfExists()](craft5:craft\helpers\MigrationHelper::dropForeignKeyIfExists()) removes a foreign key if it exists, without needing to know its exact name (oftentimes a random string).
82
+
-[dropIndexIfExists()](craft5:craft\helpers\MigrationHelper::dropIndexIfExists()) removes an index if it exists, without needing to know its exact name (oftentimes a random string).
65
83
-[dropTable()](craft5:craft\helpers\MigrationHelper::dropTable()) drops a table, along with any foreign keys that reference it (some of which your plugin might not even be aware of).
66
84
67
85
::: warning
68
86
The <yii2:yii\db\Migration::insert()>, [batchInsert()](<craft5:craft\db\Migration::batchInsert()>), and [update()](<yii2:yii\db\Migration::update()>) migration methods will automatically insert/update data in the `dateCreated`, `dateUpdated`, `uid` table columns in addition to whatever you specified in the `$columns` argument. If the table you’re working with does’t have those columns, make sure you pass `false` to the `$includeAuditColumns` argument so you don’t get a SQL error.
69
87
:::
70
88
71
89
::: tip
72
-
<craft5:craft\db\Migration> doesn’t have a method for _selecting_ data, so you will still need to go through Yii’s [Query Builder](https://www.yiiframework.com/doc/guide/2.0/en/db-query-builder) for that.
90
+
<craft5:craft\db\Migration> doesn’t have a method for _selecting_ data, so you will still need to go through Yii’s [Query Builder](https://www.yiiframework.com/doc/guide/2.0/en/db-query-builder) for read-only queries.
73
91
74
92
```php
75
93
use craft\db\Query;
@@ -78,48 +96,131 @@ $result = (new Query())
78
96
// ...
79
97
->all();
80
98
```
81
-
82
99
:::
83
100
84
101
### Logging
85
102
86
-
If you want to log messages in your migration code, echo it out rather than calling [Craft::info()](<yii2:yii\BaseYii::info()>):
103
+
If you want to log messages in your migration code, `echo` it rather than calling [Craft::info()](<yii2:yii\BaseYii::info()>):
87
104
88
105
```php
89
106
echo " > some note\n";
90
107
```
91
108
92
-
If the migration is being run from a console request, this will ensure the message is seen by whoever is executing the migration, as the message will be output into the terminal. If it’s a web request, Craft will capture it and log it to `storage/logs/` just as if you had used `Craft::info()`.
109
+
When the migration is run from the console, `echo` outputs text to the terminal (`stdout`).
110
+
For web requests, Craft captures the output and logs it to `storage/logs/`, as if you had used `Craft::info()`.
111
+
As a consequence, use of the [console command output helpers](commands.md#output-helpers) may pollute output with ANSI control characters.
93
112
94
113
## Executing Migrations
95
114
96
-
You can have Craft apply your new migration from the terminal:
115
+
You can apply your new migration from the terminal:
97
116
98
117
::: code
99
-
100
118
```bash Plugin Migration
101
119
php craft migrate/up --plugin=my-plugin-handle
102
120
```
103
-
104
121
```bash Content Migration
105
122
php craft migrate/up
106
123
```
107
-
108
124
:::
109
125
110
-
Or you can have Craft apply all new migrations across all migration tracks:
126
+
To apply _all_ new migrations, across _all_ migration tracks, run `migrate/all`:
111
127
112
128
```bash
113
129
php craft migrate/all
114
130
```
115
131
116
-
Craft will also check for new plugin migrations on control panel requests, for any plugins that have a new [schema version](craft5:craft\base\PluginTrait::$schemaVersion), and content migrations can be applied from the control panel by going to Utilities → Migrations.
132
+
Craft will also check for new plugin and content migrations on control panel requests.
133
+
App migrations must be applied before logging in; plugin and content migrations can be run later, by visiting <Journeypath="Utilities, Migrations" />.
117
134
118
-
## Plugin Install Migrations
135
+
## Rollbacks and Compatibility
136
+
137
+
### Schema Version
138
+
139
+
Your primary plugin class should maintain a [`schemaVersion`](craft5:craft\base\PluginTrait::$schemaVersion) that reflects the last release in which a migration was introduced.
140
+
When Craft notices a new schema version for a plugin, it will present control panel users with the post-upgrade “migrations” screen.
119
141
120
-
Plugins can have a special “Install” migration which handles the installation and uninstallation of the plugin. Install migrations live at `migrations/Install.php` alongside normal migrations. They should follow this template:
142
+
Despite migrations being performed incrementally, they can result in incompatible schemas, from the currently-running code’s perspective.
143
+
Keep in mind that your users may be upgrading from _any_ prior version, particularly when using your own plugin’s APIs in a migration.
144
+
For example, using a custom [element type](element-types.md)’s [query class](element-types.md#element-query-class) in a migration can result in a selection that includes columns that haven’t been added to the table yet:
121
145
122
146
```php
147
+
class ProductQuery extends ElementQuery
148
+
{
149
+
protected function beforePrepare(): bool
150
+
{
151
+
// JOIN our `products` table:
152
+
$this->joinElementTable('products');
153
+
154
+
// Always SELECT the `price` and `currency` columns...
155
+
$this->query->select([
156
+
'products.price',
157
+
'products.currency',
158
+
]);
159
+
160
+
// ...and add this column, only if it exists:
161
+
if (Craft::$app->getDb()->columnExists(MyTables::PRODUCTS, 'weight')) {
162
+
$this->query->addSelect([
163
+
'products.weight',
164
+
'products.weightUnit'
165
+
]);
166
+
}
167
+
168
+
// For performance, you can also test against schema versions that you know will contain those columns:
if (version_compare($pluginInfo['schemaVersion'], '1.2.3', '>=')) {
172
+
$this->query->addSelect([
173
+
'products.width',
174
+
'products.height',
175
+
'products.depth',
176
+
]);
177
+
}
178
+
179
+
// ...
180
+
}
181
+
}
182
+
```
183
+
184
+
::: warning
185
+
The new `schemaVersion` is only recorded after _all_ of its pending migrations have run, so a test like the one above (using `version_compare()`) may not accurately describe the state of the database.
186
+
When in doubt, explicitly check for the column’s existence.
187
+
:::
188
+
189
+
Queries built with <craft5:craft\db\Query> are typically immune to this issue, because the selections are controlled by the current migration (rather than application code).
190
+
191
+
### Minimum Versions
192
+
193
+
As a last resort, you can create a “breakpoint” in the upgrade process by setting a [`minVersionRequired`](craft5:craft\base\PluginTrait::$minVersionRequired) from which users can update.
194
+
This tends to be disruptive for developers, and means a routine upgrade must be handled across multiple deployments—even if they have applied your updates sequentially in a development environment, Craft won’t allow the jump between incompatible versions in secondary environments.
195
+
196
+
This “minimum version” also signals to Craft’s built-in updater what the latest compatible version is.
197
+
As with expired licenses, developers _can_ still directly install a more recent version via Composer—but they are apt to be met with an error as soon as plugins are loaded:
198
+
199
+
> You need to be on at least `My Plugin` 1.2.3 before you can update to `My Plugin` 1.4.0.
200
+
201
+
### Rolling Back
202
+
203
+
Another way to look at the `schemaVersion` is the farthest back a developer can expect to be able to _downgrade_ your packag, before encountering schema compatibility issues.
204
+
205
+
You may be able to provide additional support by thoroughly implementing `safeDown()` in each of your migrations.
206
+
Backtracking is handled similarly to normal upgrades; each migration’s `safeDown()` method is invoked in succession, and its record is deleted from the `migrations` table so it can be re-run.
207
+
208
+
```bash
209
+
php craft migrate/down
210
+
```
211
+
212
+
The `safeDown()` method must actually reverse changes from `safeUp()` for it to be undone (or redone) successfully.
213
+
If a migration tries to create a table or column that already exists, it will likely result in an error.
214
+
215
+
## Plugin Install Migrations
216
+
217
+
Plugins can have a special “Install” migration which handles the installation and uninstallation of the plugin.
218
+
_This is the only migration run during installation_, so it should establish your plugin’s complete database schema, in each release.
219
+
Your plugin’s other, incremental migrations are _not_ run during installation.
220
+
221
+
The special install migration should live at `migrations/Install.php`, alongside normal migrations, and follow this template:
222
+
223
+
```php{6}
123
224
<?php
124
225
namespace mynamespace\migrations;
125
226
@@ -145,7 +246,7 @@ You can give your plugin an install migration with the `migrate/create` command
When a plugin has an Install migration, its `safeUp()` method will be called when the plugin is installed, and its `safeDown()` method will be called when the plugin is uninstalled (invoked by the plugin’s [install()](<craft5:craft\base\Plugin::install()>) and [uninstall()](<craft5:craft\base\Plugin::uninstall()>) methods).
249
+
When a plugin has an install migration, its `safeUp()` method will be called when the plugin is installed, and its `safeDown()` method will be called when the plugin is uninstalled (invoked by the plugin’s [install()](<craft5:craft\base\Plugin::install()>) and [uninstall()](<craft5:craft\base\Plugin::uninstall()>) methods, respectively).
149
250
150
251
::: tip
151
252
It is _not_ a plugin’s responsibility to manage its row in the `plugins` database table. Craft takes care of that for you.
Copy file name to clipboardExpand all lines: docs/5.x/extend/plugin-editions.md
+41-9Lines changed: 41 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,30 +1,35 @@
1
1
# Plugin Editions
2
2
3
-
The Plugin Store supports multi-edition plugins, which work similarly to Craft’s two editions (Solo and Pro).
3
+
The Plugin Store supports multi-edition plugins, which work similarly to Craft’s editions (Solo, Team, and Pro).
4
4
5
5
- Plugins that support multiple editions are still comprised of a single Composer package.
6
6
- Plugins’ active edition is recorded in the [project config](../project-config.md).
7
7
- Plugins can implement feature toggles by checking their active edition.
8
+
- Plugins may require a particular Craft edition.
8
9
9
10
::: warning
10
11
Not every plugin can or should support editions. [Contact](https://craftcms.com/contact) Pixel & Tonic before you begin adding edition support to make sure it will be allowed for your plugin.
11
12
:::
12
13
13
14
## Define the Editions
14
15
15
-
To add edition support to a plugin, begin by defining the available editions (in ascending order), by overriding <craft5:craft\base\Plugin::editions()>.
16
+
To add edition support to a plugin, begin by defining the available editions (in “ascending” order), by overriding <craft5:craft\base\Plugin::editions()>.
16
17
17
18
```php
18
19
class Plugin extends \craft\base\Plugin;
19
20
{
21
+
// The order of your constants isn’t important...
20
22
const EDITION_LITE = 'lite';
21
23
const EDITION_PRO = 'pro';
24
+
const EDITION_EXTREME = 'extreme';
22
25
23
26
public static function editions(): array
24
27
{
25
28
return [
29
+
// ...but the order you return them *is*!
26
30
self::EDITION_LITE,
27
31
self::EDITION_PRO,
32
+
self::EDITION_EXTREME,
28
33
];
29
34
}
30
35
@@ -37,26 +42,27 @@ class Plugin extends \craft\base\Plugin;
37
42
Your feature toggles can call your plugin’s [is()](craft5:craft\base\Plugin::is()) method.
38
43
39
44
::: code
40
-
41
45
```php
42
46
if (Plugin::getInstance()->is(Plugin::EDITION_PRO) {
43
47
// Pro edition-only code goes here...
44
48
}
45
-
```
46
49
50
+
if (Plugin::getInstance()->is(Plugin::EDITION_LITE, '>')) {
51
+
// Advanced functionality goes here (`pro` or `extreme`)
52
+
}
53
+
```
47
54
```twig
48
55
{% if plugin('plugin-handle').is('pro') %}
49
56
{# Pro edition-only code goes here... #}
50
57
{% endif %}
51
58
```
52
-
53
59
:::
54
60
55
61
`is()` accepts two arguments, `$edition` and `$operator`.
56
62
57
63
`$edition` is the name of the edition you’re concerned with.
58
64
59
-
`$operator` is how you wish to compare that edition with the installed edition. By default it is set to `=`, which tests for version equality.
65
+
`$operator` is how you wish to compare that edition with the installed edition. by default, the editions are compared for equality
60
66
61
67
The following operators are also supported:
62
68
@@ -69,12 +75,38 @@ Operator | Tests if the active edition is ____ the given edition
69
75
`==` or `eq` | …equal to… (same as default behavior)
70
76
`!=`, `<>`, or `ne` | …not equal to…
71
77
72
-
::: tip
73
-
Changing editions should always be a lossless operation; no plugin data should change as a result of the edition change. Editions can change back and forth at any time, and plugins should have no problem rolling with it.
78
+
The active edition is considered “greater than” another if it appears _later_ in the declared [editions array](#define-the-editions), and “less than” another if it appears _earlier_.
79
+
80
+
::: warning
81
+
Changing editions should _never_ result in data or configuration loss.
82
+
Editions can change at any time, including due to accidents, like an incorrectly-merged project config change.
74
83
:::
75
84
85
+
Edition comparisons can be performed once your plugin’s `init()` method is called.
86
+
Consider binding event listeners only when they are relevant to the installed edition.
87
+
88
+
Individual [controllers](controllers.md) cannot be conditionally registered (without using distinct controller namespaces), but you _can_ check for editions in each action, or a controller’s `beforeAction()` method:
89
+
90
+
```php
91
+
public function beforeAction($action): bool
92
+
{
93
+
// This controller is only accessible to Pro + Extreme installations:
94
+
if (Plugin::getInstance()->is(Plugin::EDITION_PRO, '<')) {
95
+
return false;
96
+
}
97
+
98
+
return parent::beforeAction($action);
99
+
}
100
+
```
101
+
102
+
## Require a Craft Edition
103
+
104
+
The [`minCraftEdition`](craft5:craft\base\Plugin::$minCraftEdition) property of your plugin class can be set to any of the <craft5:craft\enums\CmsEdition>
105
+
106
+
For example, a plugins that provides some additional functionality to [user groups](../system/user-management.md#user-groups) (a feature only available in <Badgetype="edition"text="Pro"vertical="middle" />) should have a `minCraftEdition` of `CmsEdition::Pro`.
107
+
76
108
## Testing
77
109
78
110
You can toggle the active edition by changing the `plugins.<plugin-handle>.edition` property in `config/project/project.yaml`.
79
111
80
-
After changing the value to a valid edition handle (one returned by your plugin’s `editions()` method), Craft will prompt you to sync your project config YAML changes into the loaded project config. Once that’s done, your plugin’s active edition will be set to the new edition, and feature toggles should start behaving accordingly.
112
+
After changing the value to a valid edition handle (one returned by your plugin’s `editions()` method), Craft will prompt you to sync your project config YAML changes into the loaded project config. Once that’s done, your plugin’s active edition will be set to the new edition, and [feature toggles](#add-feature-toggles) should start behaving accordingly.
0 commit comments