【问题标题】:Doctrine2 Migration Using DBAL instead of $this->addSqlDoctrine2 迁移使用 DBAL 而不是 $this->addSql
【发布时间】:2017-04-19 17:42:17
【问题描述】:

所以我已经完成了一堆 Doctrine2 迁移 (https://github.com/doctrine/migrations) 但我有一个关于我正在尝试做的新迁移的问题。

我一直在深入研究该库,发现$this->addSql() 用于构建要执行的 SQL 列表,然后稍后再执行。

我想做一些事情,我选择一些数据,遍历行,基于此插入新数据,然后删除我选择的数据。这很容易适用于 DBAL 库,但我想知道,我可以在迁移中安全地使用 protected $connection 吗?或者是因为它会在我的任何$this->addSql() SQL 被执行之前执行语句?此外,这似乎会破坏我在代码中看到的 dry-run 设置。有没有人有过这种迁移的经验?有什么最佳做法吗?

以下是我想做的迁移,但我不确定 Doctrine Migrations 是否支持:

public function up(Schema $schema)
{
    // this up() migration is autogenerated, please modify it to your needs
    $this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql");

    $this->addSql("ALTER TABLE article_enclosures ADD is_scrape TINYINT(1) NOT NULL");
    $this->addSql("ALTER TABLE images DROP FOREIGN KEY FK_E01FBE6AA536AAC7");

    // now lets take all images with a scrape and convert the scrape to an enclosure
    // 
    // Select all images where not scrape_id is null (join on article_image_scrape)
    // for each image:
    //     insert into article_enclosures
    //     update image set enclosure_id = new ID
    //     delete from article_image_scrape where id...
    //
    // insert into article_enclosures select article_image_scrapes...

    $sql = "SELECT i.id img_id, e.* FROM images i JOIN article_image_scrapes e ON i.scrape_id = e.id";
    $stmt = $this->connection->prepare($sql);
    $stmt->execute();
    $scrapesToDelete = array();
    while ($row = $stmt->fetch()) {
        $scrapeArticle = $row['article_id'];
        $scrapeOldId = $row['id'];
        $scrapeUrl = $row['url'];
        $scrapeExtension = $row['extension'];
        $scrapeUrlHash = $row['url_hash'];
        $imageId = $row['image_id'];

        $this->connection->insert('article_enclosures', array(
            'url' => $scrapeUrl,
            'extension' => $scrapeExtension,
            'url_hash' => $scrapeUrlHash
        ));

        $scrapeNewId = $this->connection->lastInsertId();

        $this->connection->update('images', array(
            'enclosure_id' => $scrapeNewId,
            'scrape_id' => null
        ), array(
            'id' => $imageId
        ));

        $scrapesToDelete[] = $scrapeOldId;
    }

    foreach ($scrapesToDelete as $id) {
        $this->connection->delete('article_image_scrapes', array('id' => $id));
    }

    $this->addSql("INSERT INTO article_scrapes (article_id, url, extension, url_hash) "
            ."SELECT s.id, s.url, s.extension, s.url_hash"
            ."FROM article_image_scrapes s");

    $this->addSql("DROP INDEX IDX_E01FBE6AA536AAC7 ON images");
    $this->addSql("ALTER TABLE images DROP scrape_id, CHANGE enclosure_id enclosure_id INT NOT NULL");
}

【问题讨论】:

  • 我决定在此之前和之后使用必要的addSql 调用进行单独的迁移,以便顺序正确。
  • 你试过了吗?我觉得还可以
  • 我想是的,但这是一年前的事了。我相信原来的问题仍然存在。也就是说,使用->addSql() 调用,这些调用将最后执行。 dry-run 仍将运行您的直接操作。所以它仍然看起来很老套(但我可能是错的,从来没有得到答案,而且我不再记得太多了)。
  • 我遇到了一个类似的问题,最终使用了存储过程,因此可以将其添加为 addSql() 语句的一部分。我使用sgalinski.de/typo3-agentur/technik/… 的第 4.1 节作为我需要做的基础的一部分

标签: php doctrine-orm database-migration


【解决方案1】:

你可以像这样使用$connection

$result = $this->connection->fetchAssoc('SELECT id, name FROM table1 WHERE id = 1');
$this->abortIf(!$result, 'row with id not found');
$this->abortIf($result['name'] != 'jo', 'id 1 is not jo');
// etc..

您应该只读取数据库而不是使用连接来进行更新/删除,这样就不会破坏空运行选项。

在您的示例中,您应该进行两次迁移。第一个会做两个alter table。第二个将执行“带有刮擦的图像并将刮擦转换为外壳”例程。如果出现问题,使用多次迁移更容易恢复它们。

【讨论】:

    【解决方案2】:

    仅供参考,最新的文档显示了这个使用“postUp”方法更好的示例

    http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html

    // ...
    use Symfony\Component\DependencyInjection\ContainerAwareInterface;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    
    class Version20130326212938 extends AbstractMigration implements ContainerAwareInterface
    {
    
        private $container;
    
        public function setContainer(ContainerInterface $container = null)
        {
            $this->container = $container;
        }
    
        public function up(Schema $schema)
        {
            // ... migration content
        }
    
        public function postUp(Schema $schema)
        {
            $em = $this->container->get('doctrine.orm.entity_manager');
            // ... update the entities
        }
    }
    

    【讨论】:

    • 我想补充一点,在 Doctrine Migrations 环境中处理 EntityManager 时应该小心。 EntityManager 将使用可能与原始迁移中使用的实体定义不同的当前实体定义。我根本不会在迁移中使用 EntityManager。
    • @SteveB 也许使用DBAL 层可能是一个解决方案。我们可以访问connection 的属性\Doctrine\DBAL\Connection
    • @AdrienG 是的,我的评论更多是关于使用EntityManager 作为快捷方式的问题。虽然它看起来非常好,但事实并非如此。当最终更新实体定义时,它将导致问题。使用与应用程序当前状态没有直接关系的任何东西都很好。
    猜你喜欢
    • 1970-01-01
    • 2011-06-19
    • 2017-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多