2012-04-27 21 views
12

Vì vậy, tôi đã thực hiện một loạt các di chuyển Doctrine2 (https://github.com/doctrine/migrations) nhưng tôi có một câu hỏi cho một di cư mới tôi đang cố gắng làm.

Tôi đã đào sâu vào thư viện một chút và tôi thấy rằng $this->addSql() được sử dụng để xây dựng danh sách SQL để thực thi và sau đó nó được thực thi sau này.

Tôi muốn làm điều gì đó mà tôi chọn một số dữ liệu, lặp qua các hàng, chèn dữ liệu mới dựa trên đó, sau đó xóa dữ liệu tôi đã chọn. Điều này cho chính nó đến thư viện DBAL khá dễ dàng, nhưng tôi tự hỏi, tôi có thể sử dụng số protected $connection khi di chuyển một cách an toàn không? Hoặc là xấu bởi vì nó sẽ thực hiện các câu lệnh trước khi bất kỳ SQL nào của tôi $this->addSql() được thực thi? Ngoài ra, có vẻ như điều này sẽ phá vỡ cài đặt dry-run từ những gì tôi đã thấy trong mã. Có ai có kinh nghiệm với loại di cư này không? Có bất kỳ thực hành tốt nhất nào không?

Sau đây là cuộc di cư Tôi muốn làm, nhưng tôi không tự tin rằng đây được hỗ trợ bởi Học thuyết 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"); 
} 
+0

tôi quyết định chỉ làm di cư riêng biệt trước và sau khi trang này với cần thiết 'addSql' cuộc gọi để việc đặt hàng là chính xác. – Matt

+0

Bạn đã thử chưa? Có vẻ ok với tôi – eddy147

+0

Tôi nghĩ vậy, nhưng đây là một năm trước. Tôi tin rằng vấn đề ban đầu vẫn đứng vững. Tức là, bằng cách sử dụng các cuộc gọi '-> addSql()', những cuộc gọi đó sẽ được thực thi cuối cùng. Và 'dry-run' sẽ vẫn chạy thao tác trực tiếp của bạn. Vì vậy, nó vẫn có vẻ hacky (nhưng tôi có thể sai, không bao giờ nhận được một câu trả lời và tôi không nhớ nhiều về điều này nữa). – Matt

Trả lời

11

Bạn có thể sử dụng $connection như thế này

$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.. 

Bạn chỉ nên đọc cơ sở dữ liệu và không sử dụng kết nối để thực hiện cập nhật/xóa để nó không phá vỡ tùy chọn chạy khô.

Trong ví dụ của bạn, bạn nên thực hiện hai lần di chuyển. Việc đầu tiên sẽ làm hai bảng thay đổi. Thứ hai sẽ làm "hình ảnh với một cạo và chuyển đổi các cạo để một bao vây" thói quen. Sử dụng nhiều di chuyển dễ dàng hơn để hoàn nguyên chúng nếu có sự cố.

8

FYI, tài liệu mới nhất cho thấy ví dụ này mà thậm chí còn tốt hơn với một phương pháp "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 
    } 
} 
+5

Tôi muốn thêm rằng bạn nên cẩn thận khi giao dịch với 'EntityManager' trong môi trường Doctrine Migrations. EntityManager sẽ sử dụng các định nghĩa thực thể hiện tại có thể khác với các định nghĩa được sử dụng trong quá trình di chuyển ban đầu. Tôi sẽ không sử dụng EntityManager trong quá trình di chuyển. – SteveB

+0

@SteveB có thể sử dụng lớp 'DBAL' có thể là một giải pháp. Chúng tôi có quyền truy cập vào thuộc tính 'connection' là' \ Doctrine \ DBAL \ Connection' –

+1

@AdrienG vâng, nhận xét của tôi là nhiều hơn về vấn đề sử dụng 'EntityManager' làm lối tắt. Trong khi nó trông hoàn toàn tốt đẹp, nó không phải là. Nó sẽ gây ra vấn đề xuống dòng khi một người sẽ cập nhật định nghĩa thực thể cuối cùng. Sử dụng bất cứ điều gì không liên quan trực tiếp đến trạng thái hiện tại của ứng dụng là hoàn toàn tốt đẹp. – SteveB

Các vấn đề liên quan