在任何一個快速迭代的工程下,如何保證開發和生產(現網)數據庫同步是一個很頭疼的事情。Magento提供了一個創建資源遷移版本的系統,可以幫助我們處理開發過程中不斷遇到的這個問題。www.2cto.com 上次我們創建了weblogpost的模型。這次,我們執行直接執行CREATE TABLE。我們將未我們的module創建一個Setup Resource,而該資源會創建一個表格。我們同時也會創建一個升級的腳本,它能升級已經安裝的module。總的來說 1. 在config裡增加SetupResource 2. 創建resourceclass文件 3. 創建installerscript 4. 創建升級script 增加Setup Resource 我們在<resource/>部分增加下面的 <resources> <!-- ... --> <weblog_setup> <setup> <module>XStarX_Weblog</module> <class>XStarX_Weblog_Model_Resource_Mysql4_Setup</class> </setup> <connection> <use>core_setup</use> </connection> </weblog_setup> <!-- ... --> </resources> <weblog_setup>標簽是用來唯一表示SetupResource的。通常鼓勵使用modelname_setup。<module>XStarX_Weblog</modul>標簽下應該包含我們模塊的Pachagename_Modulename。最後<class>XStarX_Weblog_Model_Resource_Mysql4_Setup</class>應該包含我們要創建的Setup Resource類的名字。對於基本的腳本來說,沒有必要創建自己的類,但是這麼做,以後可以更靈活。 增加完配置後,清除cache,並且加載Magento Site,你會發現出異常了 Fatalerror: Class 'XStarX_Weblog_Model_Resource_Mysql4_Setup' not found in Magento試圖實例化我們在config裡聲明的類,但是沒有找到。我們需要創建這樣的類文件app/code/local/XStarX/Weblog/Model/Resource/Mysql4/Setup.php classXStarX_Weblog_Model_Resource_Mysql4_Setup extendsMage_Core_Model_Resource_Setup { } 現在重新加載Magento網站,異常就消失了。 創建安裝腳本 接下來,我們要創建安裝腳本。腳本包含了之前的CREATETABLE語句。 首先,先看一下config.xml <modules> <XStarX_Weblog> <version>0.1.0</version> </XStarx_Weblog> </modules> 這個部分在配置文件中是必備的,標示了module的同時也告訴了版本。安裝腳本要基於版本好。在下列位置創建文件 app/code/local/XStarX/Weblog/sql/weblog_setup/mysql4-install-0.1.0.php echo 'Running This Upgrade: '.get_class($this)."\n<br /> \n"; die("Exit for now"); 路徑的weblog_setup部分匹配了config.xml文件<weblog_setup/>。0.1.0部分匹配了module的版本。清除緩存,加載頁面,可以看到 Running This Upgrade:Alanstormdotcom_Weblog_Model_Resource_Mysql4_Setup Exit for now ... 這意味著我們的update腳本執行了。最終我們把SQL更新文件放在這裡,但是暫時我們把精力放在setup機制上。把die聲明去掉, echo 'Running This Upgrade:'.get_class($this)."\n <br /> \n"; 重新加載頁面,可以看到升級消息在頁面的首部分展示。重新加載,頁面將恢復正常。因為setup就一次嘛。不可能總setup。 創建安裝腳本 MagenoSetup Resources容許我們簡單的放置安裝腳本和升級腳本,然後系統就會自動執行。這容許我們系統中的數據遷移腳本保持一次。 使用database client,查看core_resroucetable mysql> select * from core_resource; +-------------------------+---------+ |code | version |+-------------------------+-----+ |adminnotification_setup | 1.0.0 | | admin_setup | 0.7.1 | | amazonpayments_setup | 0.1.2 | | api_setup | 0.8.1 | | backup_setup | 0.7.0 | | bundle_setup | 0.1.7 | | catalogindex_setup | 0.7.10 | | cataloginventory_setup | 0.7.5 | | catalogrule_setup | 0.7.7 | | catalogsearch_setup | 0.7.6 | | catalog_setup | 0.7.69 | | checkout_setup | 0.9.3 | | chronopay_setup | 0.1.0 | | cms_setup | 0.7.8 | | compiler_setup | 0.1.0 | | contacts_setup | 0.8.0 | | core_setup | 0.8.13 | | cron_setup | 0.7.1 | | customer_setup | 0.8.11 | | cybermut_setup | 0.1.0 | | cybersource_setup | 0.7.0 | | dataflow_setup | 0.7.4 | | directory_setup | 0.8.5 | | downloadable_setup | 0.1.14 | | eav_setup | 0.7.13 | | eway_setup | 0.1.0 | | flo2cash_setup | 0.1.1 | | giftmessage_setup |0.7.2 | | googleanalytics_setup | 0.1.0 | | googlebase_setup | 0.1.1 | | googlecheckout_setup | 0.7.3 | | googleoptimizer_setup | 0.1.2 | | ideal_setup | 0.1.0 | | log_setup | 0.7.6 | | newsletter_setup | 0.8.0 | | oscommerce_setup | 0.8.10 | | paybox_setup | 0.1.3 | | paygate_setup | 0.7.0 | | payment_setup | 0.7.0 | | paypaluk_setup | 0.7.0 | | paypal_setup | 0.7.2 | | poll_setup | 0.7.2 | | productalert_setup | 0.7.2 | | protx_setup | 0.1.0 | | rating_setup | 0.7.2 | | reports_setup | 0.7.7 | | review_setup | 0.7.4 | | salesrule_setup | 0.7.7 | | sales_setup | 0.9.38 | | sendfriend_setup | 0.7.2 | | shipping_setup | 0.7.0 | | sitemap_setup | 0.7.2 | | strikeiron_setup | 0.9.1 | | tag_setup | 0.7.2 | | tax_setup | 0.7.8 | | usa_setup | 0.7.0 | | weblog_setup | 0.1.0 | | weee_setup | 0.13 | | wishlist_setup | 0.7.4 | +-------------------------+---------+ 59 rowsin set (0.00 sec) 這個表格包含了所有安裝module的list,同時還有對應的版本。在表的結尾部分看到了 | weblog_setup | 0.1.0 | 這個就是Magento如何知道要不要重新執行腳本。如果都成功,頁面就會加載。Weblog_setup已經安裝了,所以不需要更新。如果想重裝腳本,需要刪除表裡的改行。我們現在可以刪除 DELETE from core_resource where code = 'weblog_setup'; 然後刪除對應的table DROP TABLE blog_posts; 接著在setup腳本裡增加 $installer = $this; $installer->startSetup(); $installer->run(" CREATE TABLE `{ $installer->getTable('weblog/blogpost')}`( `blogpost_id`int(11) NOT NULL auto_increment, `title`text, `post`text, `date`datetime default NULL, `timestamp`timestamp NOT NULL default CURRENT_TIMESTAMP, PRIMARY KEY (`blogpost_id`) ) ENGINE=InnoDBDEFAULT CHARSET=utf8; INSERTINTO `{$installer->getTable('weblog/blogpost')}` VALUES (1,'My NewTitle','This is a blog post','2009-07-01 00:00:00','2009-07-02 23:12:30'); "); $installer->endSetup(); 清除cache,加載頁面,你可以看到blog_posts又創建了,並且有一條數據。 創建安裝腳本---問題 上面的安裝可能不會那麼順利,在magento1.7下面會報錯 Mage_Eav_Exception: Can't create table: module_entity 如何解決呢? Debug createEntityTables()方法,可以在結尾處看到 $connection->beginTransaction(); try { foreach ($tables as $tableName => $table) { $connection->createTable($table); } $connection->commit(); } catch (Exception $e) { Zend_Debug::dump($e->getMessage()); $connection->rollBack(); throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Can\'t create table: %s', $tableName)); } 查看底層錯誤是:UserError: DDL statements are not allowed in transactions 然後跟進commit函數 /** * Check transaction level in case of DDL query * * @param string|Zend_Db_Select $sql * @throws Zend_Db_Adapter_Exception */ protected function _checkDdlTransaction($sql) { if (is_string($sql) && $this->getTransactionLevel() > 0) { $startSql = strtolower(substr(ltrim($sql), 0, 3)); if (in_array($startSql, $this->_ddlRoutines)) { trigger_error(Varien_Db_Adapter_Interface::ERROR_DDL_MESSAGE, E_USER_ERROR); } } } 結論是Mysql不支持DDL Transaction。 因此在app/code/local/{CompanyName}/{ModuleName}/Setup/Helper.php裡重寫createEntityTable方法 { ... /** * Remove transaction code due to issues with errors. */ //$connection->beginTransaction(); try { foreach ($tables as $tableName => $table) { $connection->createTable($table); } $connection->commit(); } catch (Exception $e) { //$connection->rollBack(); throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Can\'t create table: %s', $tableName)); } } } 然後問題解決。 Setup腳本剖析 讓我們一行一行的解釋。首先 $installer = $this; 每個安裝腳本都是從SetResource類開始執行的(就是我們上面創建的)。這意味著腳本中的$this引用是這個類實例化的引用。如果不是必須,core系統裡大部分安裝腳本都是把$this命名未installer,此處我們也是這樣。 接下來我們看到了兩個方法 $installer->startSetup(); //... $installer->endSetup(); 如果查看Mage_Core_Model_Resource_Setup類(在目錄app/code/core/Mage/Core/Resource/Setup.php),你可以看到如下的內容 public function startSetup() { $this->_conn->multi_query(" SET SQL_MODE=''; SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; "); return $this; } public function endSetup() { $this->_conn->multi_query(" SET SQL_MODE=IFNULL(@OLD_SQL_MODE,''); SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS,0); "); return $this; } 最後我們執行 $installer->run(...); 這個接受了一個包含創建數據庫的SQL。你可定義任意的查詢,通過分號隔開就好。同時,也要注意 $installer->getTable('weblog/blogpost') getTable方法容許我們把Magento Model URI傳入,然後得到它的表名。如果不是必要,就用次方法執行。Mage_Core_Model_Resource_Setup類包含了很多有用的Helper方法。最有效的學習是研究Magento core的installer scripts。 Module升級 上面講述了如何初始化數據表,但是如何改變現有墨香的結構呢?Magento的Setup Resources支持一個簡單的版本策略,可以讓我們自動的執行腳本來升級我們的模塊。 一旦Magento執行一個安裝腳本後,它就不會再次執行另外一個安裝腳本。這個時候,我們應該創建一個升級腳本。升級腳本跟安裝腳本非常類似,只有有些關鍵處不一樣。 作為開始,我們在下列位置創建一個腳本, XStarX/Weblog/sql/weblog_setup/mysql4-upgrade-0.1.0-0.2.0.php echo 'Testing our upgrade script (mysql4-upgrade-0.1.0-0.2.0.php) and halting execution to avoid updating the system version number <br />'; die(); 升級腳本和安裝腳本在同一個目錄,但是略有不同。首先,文件名要包含upgrade。其次,要有兩個版本號,並用“-”分隔。第一個是升級的源版本,第二個是升級的目標版本。 清除cache後,重新加載頁面,但這個時候腳本並沒有執行。我們需要更新config.xml裡面的版本信息來觸發升級 <modules> <Alanstormdotcom_Weblog> <version>0.2.0</version> </Alanstormdotcom_Weblog> </modules> 寫入新的版本號後,如果清除緩存,加載網站,就可以看到輸出了。這個時候還有一個關鍵點需要注意,所以先不慌做這一步。我們在同樣的目錄創建另外一個文件 XStarX/Weblog/sql/weblog_setup/mysql4-upgrade-0.1.0-0.1.5.php echo 'Testing our upgrade script (mysql4-upgrade-0.1.0-0.1.5.php) and NOT halting execution <br />'; 這個時候再清除緩存,加載頁面,可以看到兩個信息。當Magento發現版本號信息變更後,他會執行所有可執行的腳本來更新模塊。盡管我們從沒有創建0.1.5版本,但是Magento會看到升級腳本,然後嘗試執行。腳本一般按照從低到高的順序執行。下面的數據會說明這個 mysql> select * from core_resource where code = 'weblog_setup'; +--------------+---------+ | code | version | +--------------+---------+ | weblog_setup | 0.1.5 | +--------------+---------+ 1 row in set (0.00 sec) 我們看到數據表裡的版本是1.5。這是因為我們從1.0到1.5升級,但是沒有執行1.0到2.0的升級。好了,說明了這個關鍵問題後,我們言歸正傳。回到腳本上來,先修改升級腳本0.1.0-0.2.0 $installer = $this; $installer->startSetup(); $installer->run(" ALTER TABLE `{$installer->getTable('weblog/blogpost')}` CHANGE post post text not null; "); $installer->endSetup(); die("You'll see why this is here in a second"); 刷新頁面,但是什麼也不會發生。升級腳本為什麼沒有執行? 1. weblog_setup resource是版本0.1.0 2. 我們要升級模塊到0.2.0 3. Magento看到升級模塊,有兩個腳本要執行,0.1.0-0.1.5 和0.1.0-0.2.0 4. Magento載入隊列,然後執行 5. Magento執行0.1.0到0.1.5的腳本 6. Weblog_setup resource現在是0.1.5了 7. Magento執行0.1.0到0.2.0的腳本,執行停止 8. 在下一個頁面加載的時候,Magento看到了weblog_set在版本0.1.5,但是並沒有看到任何從0.1.5開始執行的腳本(之前的都是0.1.0開始) 正確的方式如下,重新命名文件 mysql4-upgrade-0.1.0-0.1.5.php #This goes from 0.1.0 to 0.1.5 mysql4-upgrade-0.1.5-0.2.0.php #This goes 0.1.5 to 0.2.0 Magento是能夠完成一次加載兩次升級的。你可以清除core_resource表信息,來完成最後的test update core_resource set version = '0.1.0' where code = 'weblog_setup'; Magento是根據配置文件來執行升級的,所以在協同開發時要注意腳本的添加。