yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.9k forks source link

migrationNamespaces in config file : mix of physical file and PSR namespace #15134

Open vinpel opened 7 years ago

vinpel commented 7 years ago

Introduction

I want to automaticly apply new migration from my Yii2 library installed in the vendor path with a yii migrate from main project

I use deployer to install app in the production server, i want to have a garanty of correlation between the code version and the database structure.

What steps will reproduce the problem?

My library is in : /vendor/mycompagny/myExtension

/vendor/mycompagny/myExtension/composer.json:

"autoload": {
  "psr-4": {
   "mycompagny\\src\\myExtension\\": ""
   }
}

(note the "src" subdirectory who make namespace!=directory)

I set up the application this way :

<?php
'controllerMap'       => [
    'migrate' => [
      'class' => 'yii\console\controllers\MigrateController',
      'migrationNamespaces' => [
          "vendor\mycompagny\myExtension\migrations",
          ...
          ]

Now if 'myExtension' have a new version with a new migration i will apply modification with :

composer update 
yii migrate

What is the expected result?

the new migration is applied

What do you get instead?

Creating migration history table "migration"...Done.
Total 5 new migrations to be applied:
    vendor\mycompagny\myExtension\migrations\m160614_144018_log_database
    vendor\mycompagny\myExtension\migrations\m160829_112632_histo_counter
    vendor\mycompagny\myExtension\migrations\m160919_124920_historique_view_all
    vendor\mycompagny\myExtension\migrations\m170427_062756_histo_index
    vendor\mycompagny\myExtension\migrations\m170607_062756_log_index
*** applying vendor\mycompagny\myExtension\migrations\m160614_144018_log_database
Exception 'ReflectionException' with message 'Class vendor\mycompagny\myExtension\migrations\m160614_144018_log_database does not exist'

in ./vendor/yiisoft/yii2/di/Container.php:426

Stack trace:
#0 ./vendor/yiisoft/yii2/di/Container.php(426): ReflectionClass->__construct('vendor\\mycompagny...')
#1 ./vendor/yiisoft/yii2/di/Container.php(364): yii\di\Container->getDependencies('vendor\\mycompagny...')
#2 ./vendor/yiisoft/yii2/di/Container.php(156): yii\di\Container->build('vendor\\mycompagny...', Array, Array)
#3 ./vendor/yiisoft/yii2/BaseYii.php(349): yii\di\Container->get('vendor\\mycompagny...', Array, Array)
#4 ./vendor/yiisoft/yii2/console/controllers/MigrateController.php(195): yii\BaseYii::createObject(Array)
#5 ./vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php(724): yii\console\controllers\MigrateController->createMigration('vendor\\mycompagny...')
#6 ./vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php(199): yii\console\controllers\BaseMigrateController->migrateUp('vendor\\mycompagny...')
#7 [internal function]: yii\console\controllers\BaseMigrateController->actionUp(0)

What is the origin of the problem ?

The docs claim "namespace setting" but this code search for a file on the disk :

https://github.com/yiisoft/yii2/blob/4e176ca3259083a8c441c70182bc10e96cbfec12/framework/console/controllers/BaseMigrateController.php#L706-L709

Actually :

For me the correct way could be :

How you could bypass the problem ?

Use this setup :

<?php
'controllerMap'       => [
    'migrate' => [
      'class' => 'yii\console\controllers\MigrateController',
      'migrationNamespaces' => [
          "mycompagny\myExtension\src\migrations",
          ...
],
    'params'                 => [],
    'aliases'                => [
        '@mycompagny' => '@vendor/mycompagny',
    ],
          ]

Creating an alias for each vendor just because YiI2 search for a @+namespace is not a good behavior for me.

Additional info

Q A
Yii version 2.0.13
PHP version 7.1
Operating system alpine
vinpel commented 7 years ago

after futher reseach it's not a bug

Documentation is not clear in the namespace config part.

It work fine if the vendor library is defined as a yii2-extension

see : http://www.yiiframework.com/doc-2.0/guide-structure-extensions.html

when you type : `composer require library

an alias is created in ./vendor/yiisoft/extensions.php :

  '@mycompagny/myExtension' => $vendorDir . 'mycompagny/myExtension/src',

so now in the namespace name :

'controllerMap'       => [
    'migrate' => [
      'class' => 'yii\console\controllers\MigrateController',
      'migrationNamespaces' => [
          "mycompagny\myExtension\migrations",
          ...
          ]

will work correctly

library with yii2 migration or mandatory to be yii2-extension

cronfy commented 6 years ago

I have same problem. I have my composer package with yii2 migrations. I did not plan to make it yii2 extension. And I think creating alias for vendor is weird.

I think that if migrationNamespaces forces to use aliases with it, and does not work without aliases, it is a bug.

schmunk42 commented 6 years ago

Do you use namespaced migrations or classic migrations? The latter should be used with migrationPath. --migrationPath accepts an array of aliases since 2.0.12 see https://github.com/yiisoft/yii2/pull/14241

Also related:

cronfy commented 6 years ago

@schmunk42 thanks for #14241, I solved my task this via migrationPath.

I am disillusioned with migrationNamespaces: it's neither simple nor intuitive. I think migrationPath approach is better since 2.0.12, as it allows:

  1. Multiple paths
  2. Path aliases

and does not require alias to be the same as namespace (namespace is just not used).

Docs state that benefit of migration namespaces is that it adds origin of the migration to the history

In general, to load migrations from different locations, $migrationNamespaces is the preferable solution as the migration name contains the origin of the migration in the history, which is not the case when using multiple migration paths.

But migrationPath could provide this too, we just need to make it support origin in some way, maybe like this:

            'migrationPath' => [
                'origin1' => '@root/migrations',
                'origin2' => '@vendor/cronfy/..../src/migrations',
            ]

To summarize: I think namespaces should not be used directly as migration origin, because namespace is subject to change. Origin must be specified separately.

schmunk42 commented 6 years ago

To summarize: I think namespaces should not be used directly as migration origin, because namespace is subject to change. Origin must be specified separately.

FYI: Before 2.0.12 I used our extension https://github.com/dmstr/yii2-migrate-command to handle multiple migrations, I modified the migration table structure to include the alias(-path), so mapping would actually possible via aliases. But this would have been a BC-break if introduced into the framework.

The table looked like this:

bildschirmfoto 2017-12-13 um 14 46 39
vinpel commented 5 years ago

I faced again the same probleme, and found a solution to correctly use just namespace for migration.

Consider the following structure :

I have a yii2 extension, directory structure as :

./src/migrations : migration file with namespace company\myExtension
.tests : tests and config file for tests

the composer.json had the following PR4 configuration :

  "autoload": {
    "psr-4": {
      "company\\myExtension\\": "src/"
    }
  },

Now i want to be able tu use the migration, here in a partial of my config file :

    'controllerMap'       => [
        'migrate' => [
            'class'               => 'yii\console\controllers\MigrateController',
            'migrationPath'       => null,
            'migrationNamespaces' => [
                'company\myExtension\migrations'
            ],
        ],
    ],

using the following command to migrate :

./vendor/bin/yii migrate --appconfig=./tests/config.php

i get the following message :

Yii Migration Tool (based on Yii v2.0.15.1)

No new migrations found. Your system is up-to-date.

the problem is that the namespace is translated to a directory, without looking for psr-4 relation.

Here is my proposed fix , avaible for discussion :

in file :

   /**
     * Returns the file path matching the give namespace.
     * @param string $namespace namespace.
     * @return string file path.
     * @since 2.0.10
     */
    private function getNamespacePath($namespace)
    {
      //search for psr4 configuration
        $autoload_psr4_file = yii::getAlias('@vendor/composer/autoload_psr4.php');
        if (is_file($autoload_psr4_file)) {
            $autoload_array = require $autoload_psr4_file;

            foreach ($autoload_array as $psr4_namespace => $physical_paths) {

                if (strcmp($psr4_namespace, substr($namespace, 0, strlen($psr4_namespace))) == 0) {

                    return str_replace('/', DIRECTORY_SEPARATOR, $physical_paths[0] . DIRECTORY_SEPARATOR . substr($namespace, strlen($psr4_namespace)));

                }
            }

        }
        //fallback
        return str_replace('/', DIRECTORY_SEPARATOR, Yii::getAlias('@' . str_replace('\\', '/', $namespace)));

    }

and now :

Yii Migration Tool (based on Yii v2.0.15.1)

Total 4 new migrations to be applied:
        company\myExension\migrations\m181122_120000_hello_world
OscarBarrett commented 5 years ago

The following should work:

composer.json

{
    ...
    "autoload": {
        "psr-4": {
          "company\\myExtension\\": "src/"
        }
    }
}

config.php

<?php

return [
    ...
    'aliases' => [
        '@company/myExtension' => '@app/src',
    ],
    'controllerMap' => [
        'migrate' => [
            'class' => 'yii\console\controllers\MigrateController',
            'migrationPath' => null,
            'migrationNamespaces' => [
                'company\myExtension\migrations',
            ],
        ],
    ],
];

Then run ./vendor/bin/yii migrate --appconfig=<path to config.php>

vinpel commented 5 years ago

i confirm that the response by @OscarBarrett nearly work for me.

When you launch the migration with the --appconfig=XXX the "@app" is where the config is located.

This work for me :

<?php

return [
    ...
    'aliases' => [
        '@company/myExtension' => '@app/../src',
    ],
    'controllerMap' => [
        'migrate' => [
            'class' => 'yii\console\controllers\MigrateController',
            'migrationPath' => null,
            'migrationNamespaces' => [
                'company\myExtension\migrations',
            ],
        ],
    ],
];