-
-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Aliases for migrations #12
Comments
Overall it complicates migrations significantly while the benefit isn't as significant. Indeed, migration name could not be adjusted but that's fine. That's the nature of it. |
In that case using namespaced migrations does not make any sense in most cases - you just can't do anything with it. Non-namespaced migrations can be moved to different directory, and after config adjustment their works fine. But you can't do the same for namespaced migration, since it affects namespace. This limits refactoring a lot. AFAIK you have a plans to change current non-namespaced migrations shipped with Yii (like |
Why would you refactor placements of migrations? |
This is side effect of project refactoring. I keep migrations in modules, and sometimes I need to remove or rename module, so I need also move migrations. I can do this only because I don't use namespaces in migrations, but I still can't fix any typo in migration name. |
I didn't think about this aspect of namespaced migrations... @klimov-paul what do you think? |
I'm giving my +1 to this issue. I use yii2 to build an on-prem solution for my customers, meaning we install our app on their servers, and they run the migrations. The current setup makes it hard to both support existing applications and new installs when there can be bugs in the migrations related to both. We experimented with writing our own plugin system so that we can enable/disable features for customers who don't need them or aren't paying for them yet, and that includes conditionally loading some migrations. That means the ordering isn't consistent across all our installs, because the plugins might be enabled from the start, where they'll just be naturally ordered by date. But, if the plugin is enabled after the fact, it'll have the migrations from the plugin ordered at the end. That sort of use-case doesn't seem well covered in the current migration system, so any features to help ease that would be greatly appreciated. I'm using For example, I do this at boot-time of the application if a plugin is enabled: app()->setModule('pluginname', [
'class' => '\app\modules\pluginname\Plugin',
]);
app()->controllerMap['migrate']['migrationNamespaces'][] = 'app\modules\pluginname\migrations'; One bug we just recently ran into last week was that some DB operations meant for one of our plugins was mistakenly put in a mainline migration. We can't just delete that migration now, because it's already been run in some places and the name is depended on because of the contents of the migrations table. I ended up having to cut the code from the mainline migration and paste it in one of our existing plugin migrations (the one with the date closest to the mistaken one). While that was a mistake on our part and it's our issue, I think there could use some improvements in the migrations system to help alleviate the pain of those kinds of situations. |
You still can do a migration that updates migrations table replacing namespace part but yes, that's a bit hackish. |
I can not see a problem with moving namespaced migrations to another directory: in order to make it work all you need to setup a path alias equal to migration namespace to another folder: Yii::setAlias('@app/migrations', '/path/to/project/new/migrations/path'); |
@klimov-paul You can't move single migration in this way. It is also more like a hack - your migrations namespace is out of sync with directory structure. |
And just how putting aliases into migration class is better? This 'hack' as you call it - is a basics of class autoloading. Similar hacks are used to replace standard helpers (like |
Example of changing namespace in migrations: yiisoft/yii2-queue@9edb0a0#diff-035fdbf9bbbbf357e2eeeeee4f737f4f |
Modification of the migration history is just the same matter as modification of the GIT history. Being published migrations can not adjusted any further as well as published GIT commits can not be modified (squash, hard reset, comment modifications etc.). You need to keep your migration intact after they being published and can be picked up by other working copies. If you wish to modify them afterwards - you will have to deal with consequences on your own. |
Using namespace in migrations you can adjust the path alias to it and move them under different folder, so this feature of non-namespace migration is actually intact. It allows you to restructurize your code while keeping migration history intact. |
I don't want to modify my migrations history, I want to modify code of my application (including migrations). I already proposed solution for this with keeping history intact (none of the existing entries in
But we already did it and we are going to do it again. |
That is the same thing: any modfications of the migrations source is a BC break over any other project working copy. You can not add, for example, extra index to some migration.
Your solution requires loading ALL migration classes source codes at once per each migration operation (up, down, history, new and so on) - otherwise it can not function. This will cause a huge performance degradation especially for the long-live projects.
My answer is you can move migrations directory to another place you like https://github.com/yiisoft/yii2/issues/14481#issuecomment-316672352 My point is the solution is already exists. You can also use 2 different namespaces: one for 'legacy' migrations and another - for the new ones (let is if you like to do so) and combine them under single config at single command without breaking anything. I can not see absolutely no necessity to rename existing migration in the project, and I see absolutely no reason to introduce a huge twisted codebase in order to support it. I understand you wish to keep your code clean, but keeping those
First of all we did nothing yet: #13141 is not merged, while actions of mister @zhuravljov are beyond the reach of @yiisoft. If you have a concern about his commit - you should contact him instead. However, I should admit that future of code built-in migrations is questionable and at some point thier modifications or thier removal will be inevitable. That is why I always against introduction and usage of external migrations in the code. Still so far I am unable to convince the rest of the team on this matter, so it is for them to decide what to do here. |
No, it's not. It is BC break only when I change migration logic. Changing name of migration does not change it's logic - it will work exactly the same as before change. And sometimes you actually must edit old migrations, to make it compatible with new version of framework or PHP.
You are wrong again. You never need to load all migrations on But if this is still a problem for you, I'm sure that this could be optimized. |
Is that so? |
It works in the same way as installing app from scratch - if you have 1k migrations, first installation will load them all and run. But it will not load unchanged migrations. |
So you propose to save both migration legacy name (alias) and new name in the history doubling the entries in the history table? |
Yes. |
In that case how history reverting (migrate/down) should work if same migration is mentioned several times there? Besides It is better to create a separated command or console command behavior, which will just iterate migration history replacing old names with new ones. |
It is explained in first post - did you read it?
And how it is supposed to be better? It looks event more complicated to implement and definitely less convenient for end user (he should always run two commands except one for database update?). |
??? Here is an implementation: <?php
namespace yii\filters;
use yii\base\ActionFilter;
use yii\console\controllers\MigrateController;
use yii\db\Connection;
use yii\db\Query;
use yii\di\Instance;
/**
* ```php
* return [
* 'controllerMap' => [
* 'migrate' => [
* 'class' => 'yii\console\controllers\MigrateController',
* 'as versionReplacer' => [
* 'class' => 'yii\filters\AdjustHistoryFilter',
* 'versionReplaces' => [
* 'm140506_102106_rbac_init' => 'yii\rbac\M140506102106RbacInit',
* ],
* ],
* 'migrationNamespaces' => [
* 'app\migrations',
* 'some\extension\migrations',
* ],
* ],
* ],
* ];
* ```
*/
class AdjustHistoryFilter extends ActionFilter
{
/**
* @var array versions to be replaced in format: `[legacyVersion => newVersion]`, for example:
*
* ```php
* [
* 'm140506_102106_rbac_init' => 'yii\rbac\M140506102106RbacInit',
* ]
* ```
*/
public $versionReplaces = [];
/**
* @inheritdoc
*/
public function beforeAction($action)
{
if ($action->id === 'create') {
return true;
}
/* @var $migrateController MigrateController */
$migrateController = $action->controller;
$migrateController->migrationTable;
$migrateController->db = Instance::ensure($migrateController->db, Connection::className());
$legacyVersions = array_keys($this->versionReplaces);
$existingLegacyVersions = (new Query())
->select(['version'])
->from($migrateController->migrationTable)
->andWhere(['in', 'version', $legacyVersions])
->column($migrateController->db);
if (empty($existingLegacyVersions)) {
return true;
}
foreach ($existingLegacyVersions as $legacyVersion) {
$newVersion = $this->versionReplaces[$legacyVersion];
$migrateController->db->createCommand()
->update($migrateController->migrationTable, ['version' => $newVersion], ['version' => $legacyVersion])
->execute();
}
return true;
}
} |
It will adjust migration history automatically on any migrate command execution. |
|
Personally I solved this problem using a @klimov-paul's way. But yes, it would be nice to refactor class names/namespaces without all these tricks. And I don't see a clean way beside an UUID usage in each migration. But it starts suffering as a project gets bigger. |
Reverting of what?
I think it is much more reliable. And if I use some extension, which has some migration renamed I should enforce its author to provide these aliases even if he values his clean code. I suppose I should hunt him down and turture him until he agrees or something. While picking up any external code you are doing it on your own risk. And external library breaks a BC, including migration renaming - it should provide UPGRADE note, which you should apply on your own.
That is exactly what this issue is about! Migration class name IS A PART OF THE HISTORY as it serves as version ID. Modification of the migration class name means necessity of modification of the history. Migrations are not ordinally classes, which can be manipulated as you like - they are special are require special treatment. |
So a version ID should not depend on this in 2.1. Probably… |
This is unlikely to be possible as program should be able to find migration source using its ID.
Then history table will contain only UIDs, but this require registering every migration in the versions map. However in general versions map can be generated on the fly, while picking up new migrations, and then be adjusted at will. But still it looks like overcomplecating things. |
Migrations. If I upgrade some module from 1.x to 2.x (which changes migrations names), apply migrations and find some regressions, I want to go back to 1.x. Normally I revert migrations, revert to 1.x version of module and I should have a state before whole changes. This is not the case with your solution.
Thats because framework does not provide any tools for such situations.That does not mean that it always has to be in this way - if there is a way to improve this, why don't do this?
Migrations history is in |
Alternative if we keep this, there should be explicit note in documentation, that modules/extensions consumer should never load migrations directly from module/extension. So basically he should never do something like this:
Source: http://www.yiiframework.com/doc-2.0/yii-rbac-dbmanager.html He should create own migration that extends migrations provided with framework/extension/module and add new migrations manually on every |
@klimov-paul I do agree with you. And yes, the solution you proposed is what I'm thinking about: an id to class map & a migration map scanner/builder. |
I just want to point out how Laravel does migrations, it's worth discussing the differences to see what good ideas they might have implemented to help improve migrations in Yii https://laravel.com/docs/5.4/migrations |
Laravel's |
BTW Phinx works similar to the comment: it loads all migration classes and uses a |
Yes, that could be a solution. |
Just for reference: https://forum.yiiframework.com/t/alternative-approach-to-migrations/126274 |
Migrations names can't be changed after release, because it will break any installation that already applied them. This means that:
Basically, migrations can be really PITA for long-term projects, especially if you decide to use namespaced migrations.
I propose to add new static property to
yii\console\Migration
:On
migration/up
-
), without runningup()
method.up()
, add migration (without prefix for name) and all aliases (with prefix) to history with current timestamp.On
migrate/down
down()
. Back to step 1. (removing prefixed migration from history should not count as revert).down()
, remove migration from history, and all related aliases (so if we're revertingm141106_185632_log_init
we should also removeyii\log\migrations\M141106185632LogInit
andyii\log\migrations\M141106185632LogInt
from history).Tricky part
The obvious problem is finding real migration class for an alias (if we're reverting
m141106_185632_log_init
we need actually revertyii\log\migrations\M141106185632LogInit
). In the worst case we could scan all migrations and use$aliases
values to create alias => real-class map. But usually we could avoid this - in most cases we first load a real class, so we could start building map from already loaded migrations and do a full scan only if migration to revert can not be found.Funding
The text was updated successfully, but these errors were encountered: