So I've done a bunch of Doctrine2 migrations (https://github.com/doctrine/migrations) but I have a question for a new migration I'm trying to do.
I've been digging into the library a little and I see that $this->addSql()
is used to build a list of SQL to execute and then it gets executed later.
I wanted to do something where I select some data, iterate over the rows, insert new data based on that, and then delete the data I selected. This lends itself to the DBAL library pretty easily, but I'm wondering, can I use the protected $connection
in a migration safely? Or is that bad because it would execute statements before any of my $this->addSql()
SQL gets executed? Also it seems like this would break the dry-run
setting from what I've seen in the code. Has anyone had any experience with this type of migration? Are there any best practices?
The following is the migration I want to do, but I'm not confident that this is supported by 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");
}
You can use the $connection
like this
$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..
You should only read the database and not use the connection to make update/delete so it won't break the dry-run option.
In your example, you should do two migrations. The first will do the two alter table. The second will do the "images with a scrape and convert the scrape to an enclosure" routine. Using multiple migration is easier to revert them if something goes wrong.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With