Как написать миграцию Doctrine, которая может перераспределять данные в новые таблицы
У меня есть база данных (которая на самом деле была создана с использованием Propel в приложении Symfony1). Я переопределяю это в Symfony2 и Doctrine, но я также хочу воспользоваться возможностью немного реорганизовать базу данных.
Я определил набор сущностей Doctrine и запустил doctrine:migrations:diff, который создал базовую миграцию для добавления таблиц, столбцов и ограничений и удаления нагрузки на столбцы.
Однако, прежде чем отбрасывать эти столбцы, я хочу скопировать данные в некоторые из новых таблиц, а затем связать новые записи в этих таблицах с новыми столбцами в первой таблице. Я не верю, что это возможно сделать в чистом SQL (в общем, содержимое одной таблицы распределяется между тремя или четырьмя таблицами).
Это дало мне подсказку и заставило меня найти это (которое я пропустил, потому что я понятия не имел, какое отношение "контейнеры" могут иметь к моей проблеме).
Но то, что я не нашел нигде в документации Symfony или Doctrine, является примером фактического перемещения данных в процессе миграции, что, мне кажется, является одной из основных целей миграции!
Вполне возможно, что я мог бы использовать подсказки в этих ссылках выше, но тогда я не уверен, что делать дальше. У меня нет (и я действительно не хочу тратить время на создание, хотя я уверен, что смогу это сделать) сущности Doctrine для существующей схемы базы данных: могу ли я тогда использовать DQL? Я просто не знаю.
Итак, два вопроса:
Может кто-нибудь привести пример миграции Doctrine, которая перемещает данные между таблицами?
В качестве альтернативы, кто-нибудь может прояснить, насколько зависим синтаксис DQL от определений сущностей в доктрине? Могу ли я использовать его для указания столбцов, которых нет в определениях сущностей?
1 ответ
Хорошо, я, кажется, нашел это из ряда источников (включая этот) и методом проб и ошибок.
Комментарии Керада немного помогли, но в основном я делаю это, используя слой DBAL для чтения данных (к которым я могу получить $this->connection
) и ORM для сохранения новых данных (для этого требуется EntityManager, поэтому мне пришлось использовать трюк с контейнером).
Я положил весь код в postUp()
, включая сгенерированный код для удаления столбцов из таблиц.
Примеры битов моего кода:
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use PG\InventoryBundle\Entity\Item;
use PG\InventoryBundle\Entity\Address;
.
.
.
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20140519211228 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
.
.
.
}
}
public function postUp(Schema $schema)
{
$em = $this->container->get('doctrine.orm.entity_manager');
// ... update the entities
$query = "SELECT * FROM item";
$stmt = $this->connection->prepare($query);
$stmt->execute();
// We can't use Doctrine's ORM to fetch the item, because it has a load of extra fields
// that aren't in the entity definition.
while ($row = $stmt->fetch()) {
// But we will also get the entity, so that we can put addresses in it.
$id = $row['id'];
$item = $em->getRepository('PGInventoryBundle:Item')->find($id);
// And create new objects
$stock = new Stock();
.
.
.
$stock->setAssetNo($row['asset_no']);
$stock->setItemId($row['id']);
$em->persist($stock);
$em->flush();
}
// Now we can drop fields we don't need.
$this->connection->executeQuery("ALTER TABLE item DROP container_id");
$this->connection->executeQuery("ALTER TABLE item DROP location_id");
.
.
.
}