标签归档:Magento开发文档

Magento开发文档:Varien Data Collections

Originally, as a PHP programmer, if you wanted to collect together a group of related variables you had one choice, the venerable Array. While it shares a name with C’s array of memory addresses, a PHP array is a general purpose dictionary like object combined with the behaviors of a numerically indexed mutable array. 最早的时候,作为一个PHP程序员,如果你想把一组相关的变量集合到一起,你只有一个选择,那就是使用原始的Array数组。

In other languages the choice isn’t so simple. You have multiple data structuresto chose from, each offering particular advantages in storage, speed and semantics. The PHP philosophy was to remove this choice from the client programmer and give them one useful data structure that was “good enough”. 在其它编程语言里这个选择就不是那么简单了。你有多种数据结构可以选择,每个都在存储速度和语法上提供了特殊的优势。 PHP的哲学是从客户端程序员端移除这些选择并且提供给他们“足够好”有用的数据结构。

All of this is galling to a certain type of software developer, and PHP 5 set out to change the status quo by offering built-in classes and interfaces that allow you to create your own data structures. 所有这一切对于某个特定类型的软件开发人员来说非常难堪,但是PHP5通过内建类和接口使得你能创建自己的数据结构来试图改变这种状况。

$array = new ArrayObject();
class MyCollection extends ArrayObject{...}
$collection = new MyCollection();
$collection[] = 'bar';

While this is still galling to a certain type of software developer, as you don’t have access to low level implementation details, you do have the ability to create array-like Objects with methods that encapsulate specific functionality. You can also setup rules to offer a level of type safety by only allowing certain kinds of Objects into your Collection. 对于某种特定开发人员来说这仍然是难堪的,但是你不需要访问到底层实现细节,你就可以通过方法封装特定的功能创建类似数组的对象。你也可以设置规则,通过只允许特定类型的对象可以加入到你的集合来提供一个类型安全层。

It should come as no surprise that Magento offers you a number of these Collections. 毫无疑问,Magento提供了一系列这样的集合。In fact, every Model object that follows the Magento interfaces gets a Collection type for free. 事实上每种模型对象在Magento都提供了一种集合类型。Understanding how these Collections work is a key part to being an effective Magento programmer. 理解这些集合的工作原理是作为一个高效的Magento程序员的关键部分。We’re going to take a look at Magento Collections, starting from the bottom and working our way up. Set up a controller action where you can run arbitrary code, and let’s get started.

A Collection of Things
First, we’re going to create a few new Objects. 首先我们将创建一些新对象

$thing_1 = new Varien_Object();
$thing_1->setName('Richard');
$thing_1->setAge(24);

$thing_2 = new Varien_Object();
$thing_2->setName('Jane');
$thing_2->setAge(12);

$thing_3 = new Varien_Object();
$thing_3->setName('Spot');
$thing_3->setLastName('The Dog');
$thing_3->setAge(7);

The Varien_Object class defines the object all Magento Models inherit from. 所有的Magento模型都从Varien_Object类定义的对象继承。 This is a common pattern in object oriented systems, and ensures you’ll always have a way to easily add methods/functionally to every object in your system without having to edit every class file. 这在面向对象系统中是一个常用的模式,这可以确保你总是有办法添加方法或函数到每个对象中去,而不用修改类文件。

Any Object that extends from Varien_Object has magic getter and setters that can be used to set data properties. Give this a try 每个继承自Varien_Object的对象都拥有用来设置数据属性的getter和setter魔术方法。

var_dump($thing_1->getName());

If you don’t know what the property name you’re after is, you can pull out all the data as an array 如果你不知道在get后接上什么属性,你可以把数组以数组的方式输出

var_dump($thing_3->getData());

The above will give you an array something like

array
'name' => string 'Spot' (length=4)
'last_name' => string 'The Dog' (length=7)
'age' => int 7

Notice the property named “last_name”? If there’s an underscore separated property, you camel case it if you want to use the getter and setter magic. 注意命名为last_name的属性,如果有下划线来分隔属性名,在使用getter和setter模式方法时要转换为驼峰命名(camel case)。

$thing_1->setLastName('Smith');

The ability to do these kinds of things is part of the power of PHP5, and the development style a certain class of people mean when they say “Object Oriented Programming”. 能够做到这些事情是强大的PHP5的一部分….
So, now that we have some Objects, let’s add them to a Collection. Remember, a Collection is like an Array, but is defined by a PHP programmer. 现在我们有了一些对象,让我们添加它们到集合中。记住,一个集合类似一个数组,但是它是被PHP程序员定义的。

$collection_of_things = new Varien_Data_Collection();
$collection_of_things
    ->addItem($thing_1)
    ->addItem($thing_2)
    ->addItem($thing_3);

The Varien_Data_Collection is the Collection that most Magento data Collections inherit from. Any method you can call on a Varien_Data_Collection you can call on Collections higher up the chain (We’ll see more of this later) 大部分Magento数据集从Varien_Data_Collection继承。 任何继承了这个类的集合类都能调用它的方法。

What can we do with a Collection? For one, with can use foreach to iterate over it 集合可以作什么? 第一,可以使用foreeach来迭代它(循环)

foreach($collection_of_things as $thing)
{
    var_dump($thing->getData());
}

There are also shortcuts for pulling out the first and last items 要获取第一个和最后一个数据,有如下快捷方式:

var_dump($collection_of_things->getFirstItem()->getData());
var_dump($collection_of_things->getLastItem()->getData());

Want your Collection data as XML? There’s a method for that 还可以以XML输出

var_dump( $collection_of_things->toXml() );

Only want a particular field?

var_dump($collection_of_things->getColumnValues('name'));

The team at Magento have even given us some rudimentary filtering capabilities. Magento团队甚至还给我们提供了一些基础的过滤方法。

var_dump($collection_of_things->getItemsByColumnValue('name','Spot'));

Model Collections
So, this is an interesting exercise, but why do we care? 这是一个有趣的体验,但是我们为何要关注它呢?

We care because all of Magento’s built in data Collections inherit from this object. That means if you have, say, a product Collection you can do the same sort of things. Let’s take a look
我们关注它是因为所有Magento的内建的数据集合都继承这个对象(类)。也就是说,对于产品集,你可以使用同样的方法

public function testAction()
{
    $collection_of_products = Mage::getModel('catalog/product')->getCollection();
    var_dump($collection_of_products->getFirstItem()->getData());
}

Most Magento Model objects have a method named getCollection which will return a collection that, by default, is initialized to return every Object of that type in the system. 大部分的Magento模型对象有一个叫getCollection的方法…(后面这句话大意是返回一个这个模型对象的集合对象,它里面每个对象都是这个模型类型的对象)

A Quick Note: Magento’s Data Collections contain a lot of complicated logic that handles when to use an index or cache, as well as the logic for the EAV entity system. Successive method calls to the same Collection over its life can often result in unexpected behavior. Because of that, all the of the following examples are wrapped in a single method action. I’d recommend doing the same while you’re experimenting. Also, XDebug’s var_dump is a godsend when working with Magento Objects and Collections, as it will (usually) intelligently short circuit showing hugely recursive Objects, but still display a useful representation of the Object structure to you. 当使用索引或缓存时,Magento的数据集包含很多复杂的处理逻辑,就跟EAV实体系统逻辑一样。在它的生存期内对集合进行连续的方法调用可能经常导致意外的行为。由于这个原因,下面的所有例子都放在一个单独的方法中。…..

The products Collection, as well as many other Magento Collections, also have theVarien_Data_Collection_Db class in their ancestor chain. This gives us a lot of useful methods. For example, if you want to see the select statement your Collection is using产品集合,和很多其它的Magento集合一样,在它们的继承的祖先链中有Varien_Data_Collection_Db类(意思时它们也继承了这个类)。该类提供了很多有用的方法。例如,如果你想查看某个集合使用的select查询语句:

public function testAction()
{
    $collection_of_products = Mage::getModel('catalog/product')->getCollection();
    var_dump($collection_of_products->getSelect()); //might cause a segmentation fault
}

The output of the above will be

object(Varien_Db_Select)[94]
  protected '_bind' =>
    array
      empty
  protected '_adapter' =>
...

Whoops! Since Magento is using the Zend database abstraction layer, your Select is also an Object. Let’s see that as a more useful string. 因为Magento使用Zend数据库抽象成,所以你的Select也是一个对象。我们把它转化为有用的字符串来查看:

public function testAction()
{
    $collection_of_products = Mage::getModel('catalog/product')->getCollection();
    //var_dump($collection_of_products->getSelect()); //might cause a segmentation fault
    var_dump(
        (string) $collection_of_products->getSelect()
    );
}

Sometimes this is going to result in a simple select

'SELECT `e`.* FROM `catalog_product_entity` AS `e`'

Other times, something a bit more complex

string 'SELECT `e`.*, `price_index`.`price`, `price_index`.`final_price`, IF(`price_index`.`tier_price`, LEAST(`price_index`.`min_price`, `price_index`.`tier_price`), `price_index`.`min_price`) AS `minimal_price`, `price_index`.`min_price`, `price_index`.`max_price`, `price_index`.`tier_price` FROM `catalog_product_entity` AS `e`
INNER JOIN `catalog_product_index_price` AS `price_index` ON price_index.entity_id = e.entity_id AND price_index.website_id = '1' AND price_index.customer_group_id = 0'

The discrepancy depends on which attributes you’re selecting, as well as the aforementioned indexing and cache. If you’ve been following along with the other articles in this series, you know that many Magento models (including the Product Model) use an EAV system. By default, a EAV Collection will not include all of an Object’s attributes. You can add them all by using the addAttributeToSelect method 这些不同取决于你选择的属性,还有前面提到过的索引和缓存。如果你看过这个系列的文章,你知道很多Magento模型(包含产品模型)使用EAV系统。默认,EAV集合不会包含所有的对象的属性。你可以通过使用addAttributeToSelect方法来添加它们。

$collection_of_products = Mage::getModel('catalog/product')
    ->getCollection()
    ->addAttributeToSelect('*');  //the asterisk is like a SQL SELECT * FROM ...

Or, you can add just one

//or just one
$collection_of_products = Mage::getModel('catalog/product')
    ->getCollection()
    ->addAttributeToSelect('meta_title');

or chain together several

//or just one
$collection_of_products = Mage::getModel('catalog/product')
    ->getCollection()
    ->addAttributeToSelect('meta_title')
    ->addAttributeToSelect('price');

Lazy Loading
One thing that will trip up PHP developers new to Magento’s ORM system is when Magento makes its database calls. When you’re writing literal SQL, or even when you’re using a basic ORM system, SQL calls are often made immediately when instantiating an Object. 对于刚接触Magento ORM的PHP开发则对于Magento数据库的调用这个事情会很头疼。当你编写纯粹的SQL或使用基本的ORM系统时,SQL通常在对象实例化后马上被调用。

$model = new Customer();
//SQL Calls being made to Populate the Object
echo 'Done'; //execution continues

Magento doesn’t work that way. Instead, the concept of Lazy Loading is used. In simplified terms, Lazy loading means that no SQL calls are made until the client-programmer needs to access the data. That means when you do something something like this Magento不是以这种方式工作的。Lazy Loading的概念被使用。简单的说,Lazy loading意思是如果客户端程序员需要访问数据时SQL调用才被触发。

$collection_of_products = Mage::getModel('catalog/product')
    ->getCollection();

Magento actually hasn’t gone out to the database yet. You can safely add attributes later Magento实际上并没有运行任何数据库操作。之后你仍然可以继续添加属性。

$collection_of_products = Mage::getModel('catalog/product')
    ->getCollection();
$collection_of_products->addAttributeToSelect('meta_title');

and not have to worry that Magento is making a database query each time a new attribute is added. The database query will not be made until you attempt to access an item in the Collection. 并且在每次一个新的属性添加的时候不需要担心Magento去做一次查询。直到你试图获取集合中的数据时数据库查询才被执行。

In general, try not to worry too much about the implementation details in your day to day work. It’s good to know that there’s s SQL backend and Magento is doing SQLy things, but when you’re coding up a feature try to forget about it, and just treat the objects as block boxes that do what you need. 一般在日常工作中不需要太多担心实现的细节。知道SQL在后端并且Magent在做SQL的事情是很好的,但是当你编写一个功能时要试图忘记它,你只需要把对象当做块框来做你需要做的。

Filtering Database Collections
The most important method on a database Collection is addFieldToFilter. This adds your WHERE clauses to the SQL query being used behind the scenes. Consider this bit of code, run against the sample data database (substitute your own SKU is you’re using a different set of product data)在数据库集合上最重要的方法是addFieldToFilter。这个将添加WHERE子句到SQL查询语句中。

public function testAction()
{
    $collection_of_products = Mage::getModel('catalog/product')
        ->getCollection();
    $collection_of_products->addFieldToFilter('sku','n2610');

    //another neat thing about collections is you can pass them into the count      //function.  More PHP5 powered goodness
    echo "Our collection now has " . count($collection_of_products) . ' item(s)';
    var_dump($collection_of_products->getFirstItem()->getData());
}

The first parameter of addFieldToFilter is the attribute you wish to filter by. The second is the value you’re looking for. Here’s we’re adding a sku filter for the value n2610. addFieldToFilter的第一个参数是你希望过滤的属性。 第二个是你正要查找的值。 这里我们添加sku对应值n2610。

The second parameter can also be used to specify the type of filtering you want to do. This is where things get a little complicated, and worth going into with a little more depth. 第二个参数还能用来指定你希望过滤的类型。这个有点复杂,但值得深入学习。
So by default, the following

$collection_of_products->addFieldToFilter('sku','n2610');

is (essentially) equivalent to

WHERE sku = "n2610"

Take a look for yourself. Running the following

public function testAction()
{
    var_dump(
    (string)
    Mage::getModel('catalog/product')
    ->getCollection()
    ->addFieldToFilter('sku','n2610')
    ->getSelect());
}

will yield

SELECT `e`.* FROM `catalog_product_entity` AS `e` WHERE (e.sku = 'n2610')

Keep in mind, this can get complicated fast if you’re using an EAV attribute. Add an attribute(谨记于心,如果使用EAV属性的时候,这些很快就会变得非常复杂。添加一个属性)

var_dump(
    (string)
    Mage::getModel('catalog/product')
        ->getCollection()
        ->addAttributeToSelect('*')
        ->addFieldToFilter('meta_title','my title')
        ->getSelect()
);

and the query gets gnarly.

SELECT `e`.*, IF(_table_meta_title.value_id>0, _table_meta_title.value, _table_meta_title_default.value) AS `meta_title`
FROM `catalog_product_entity` AS `e`
INNER JOIN `catalog_product_entity_varchar` AS `_table_meta_title_default`
    ON (_table_meta_title_default.entity_id = e.entity_id) AND (_table_meta_title_default.attribute_id='103')
    AND _table_meta_title_default.store_id=0
LEFT JOIN `catalog_product_entity_varchar` AS `_table_meta_title`
    ON (_table_meta_title.entity_id = e.entity_id) AND (_table_meta_title.attribute_id='103')
    AND (_table_meta_title.store_id='1')
WHERE (IF(_table_meta_title.value_id>0, _table_meta_title.value, _table_meta_title_default.value) = 'my title')

Not to belabor the point, but try not to think too much about the SQL if you’re on deadline.

Other Comparison Operators
I’m sure you’re wondering “what if I want something other than an equals by query”? Not equal, greater than, less than, etc. The addFieldToFilter method’s second parameter has you covered there as well. It supports an alternate syntax where, instead of passing in a string, you pass in a single element Array.

The key of this array is the type of comparison you want to make. The value associated with that key is the value you want to filter by. Let’s redo the above filter, but with this explicit syntax

public function testAction()
{
    var_dump(
        (string)
        Mage::getModel('catalog/product')
            ->getCollection()
            ->addFieldToFilter('sku', array('eq'=>'n2610'))
            ->getSelect()
    );
}

Calling out our filter

addFieldToFilter('sku',array('eq'=>'n2610'))

As you can see, the second parameter is a PHP Array. Its key is eq, which stands for equals. The value for this key is n2610, which is the value we’re filtering on. 正如你看到的,第二个参数是一个PHP数组。它的键是eq,它是表中的等于,键对应的值是n2610,那是将要过滤的值。

Magento has a number of these english language like filters that will bring a tear of remembrance (and perhaps pain) to any old perl developers in the audience.
Listed below are all the filters, along with an example of their SQL equivalents.

array("eq"=>'n2610')
WHERE (e.sku = 'n2610')

array("neq"=>'n2610')
WHERE (e.sku != 'n2610')

array("like"=>'n2610')
WHERE (e.sku like 'n2610')

array("nlike"=>'n2610')
WHERE (e.sku not like 'n2610')

array("is"=>'n2610')
WHERE (e.sku is 'n2610')

array("in"=>array('n2610'))
WHERE (e.sku in ('n2610'))

array("nin"=>array('n2610'))
WHERE (e.sku not in ('n2610'))

array("notnull"=>true)
WHERE (e.sku is NOT NULL)

array("null"=>true)
WHERE (e.sku is NULL)

array("gt"=>'n2610')
WHERE (e.sku > 'n2610')

array("lt"=>'n2610')
WHERE (e.sku < 'n2610')

array("gteq"=>'n2610')
WHERE (e.sku >= 'n2610')

array("moreq"=>'n2610') //a weird, second way to do greater than equal
WHERE (e.sku >= 'n2610')

array("lteq"=>'n2610')
WHERE (e.sku <= 'n2610')

array("finset"=>array('n2610'))
WHERE (find_in_set('n2610',e.sku))

array('from'=>'10','to'=>'20')
WHERE e.sku >= '10' and e.sku <= '20'

Most of these are self explanatory, but a few deserve a special callout

>in, nin, find_in_set
The in, nin and finset conditionals allow you to pass in an Array of values. That is, the value portion of your filter array is itself allowed to be an array.

array("in"=>array('n2610','ABC123')
WHERE (e.sku in ('n2610','ABC123'))

>notnull, null
The keyword NULL is special in most flavors of SQL. It typically won’t play nice with the standard equality (=) operator. Specifying notnull or null as your filter type will get you the correct syntax for a NULL comparison while ignoring whatever value you pass in

array("notnull"=>true)
WHERE (e.sku is NOT NULL)

>from – to filter
This is another special format that breaks the standard rule. Instead of a single element array, you specify a two element array. One element has the key from, the other element has the key to. As the keys indicated, this filter allows you to construct a from/to range without having to worry about greater than and less than symbols

public function testAction
{
    var_dump(
        (string)
        Mage::getModel('catalog/product')
            ->getCollection()
            ->addFieldToFilter('price',array('from'=>'10','to'=>'20'))
            ->getSelect()
    );
}

The above yields

WHERE (_table_price.value >= '10' and _table_price.value <= '20')

AND or OR, or is that OR and AND?
Finally, we come to the boolean operators. It’s the rare moment where we’re only filtering by one attribute. Fortunately, Magento’s Collections have us covered. You can chain together multiple calls to addFieldToFilterto get a number of “AND” queries.

function testAction()
{
    echo
        (string)
        Mage::getModel('catalog/product')
            ->getCollection()
            ->addFieldToFilter('sku',array('like'=>'a%'))
            ->addFieldToFilter('sku',array('like'=>'b%'))
            ->getSelect();
}

By chaining together multiple calls as above, we’ll produce a where clause that looks something like the the following

WHERE (e.sku like 'a%') AND (e.sku like 'b%')

To those of you that just raised your hand, yes, the above example would always return 0 records. No sku can begin with BOTH an a and a b. What we probably want here is an OR query. This brings us to another confusing aspect ofaddFieldToFilter’s second parameter. 我们需要的是OR而不是这里的AND

If you want to build an OR query, you need to pass an Array of filter Arrays in as the second parameter. I find it’s best to assign your individual filter Arrays to variables

public function testAction()
{
    $filter_a = array('like'=>'a%');
    $filter_b = array('like'=>'b%');
}

and then assign an array of all my filter variables

public function testAction()
{
    $filter_a = array('like'=>'a%');
    $filter_b = array('like'=>'b%');
    echo
        (string)
        Mage::getModel('catalog/product')
            ->getCollection()
            ->addFieldToFilter('sku', array($filter_a, $filter_b))
            ->getSelect();
}

In the interest of being explicit, here’s the aforementioned array of filter arrays.

array($filter_a, $filter_b)

This will gives us a WHERE clause that looks something like the following

WHERE (((e.sku like 'a%') or (e.sku like 'b%')))

永久连接: http://blog.ifeeline.com/401.html
转载请保留出处。

Magento开发文档:Magento EAV模型

In the first ORM article we told you there were two kinds of Models in Magento. Regular, or “simple” Models, and Entity Attribute Value (or EAV) Models. We also told you this was a bit of a fib. Here’s where we come clean.在第一篇介绍Magento ORM的文章中,我们提到过Magento拥有两类模型。普通的模型及Entity Attribute Value(EAV)模型。这里首先搞清楚它们之前的一些关系。

ALL Magento Models interacting with the database inherit from theMage_Core_Model_Abstract / Varien_Object chain. What makes something either a simple Model or an EAV Model is its Model Resource. While all resources extend the baseMage_Core_Model_Resource_Abstract class, simple Models have a resource that inherits from Mage_Core_Model_Resource_Db_Abstract, and EAV Models have a resource that inherits fromMage_Eav_Model_Entity_Abstract
If you think about it, this makes sense. As the end-programmer-user of the system you want a set of methods you can use to talk to and manipulate your Models. You don’t care what the back-end storage looks like, you just want to get properties and invoke methods that trigger business rules.所有的Magento模型都继承自Mage_Core_Model_Abstract/Varien_Object类链。真正区别普通模型和EAV模型的关键是该模型使用的模型资源(Model Resource)。尽管所有的资源类都继承自Mage_Core_Model_Resource_Abstract类,普通模型拥有继承自该类的子类Mage_Core_Model_Resource_Db_Abstract (以前是Mage_Core_Model_Mysql4_Abstract,为了兼容,这个类现在只是简单继承Mage_Core_Model_Resource_Db_Abstract),同时EAV模型拥有继承自该类的另外一个子类Mage_Eav_Model_Entity_Abstract。
为什么要这样设计呢?仔细想想,不难得出结论。作为终端程序员,你需要的只是如何与数据库交互的方法,而不用在意底层是如何实现的。

What is EAV
Another metaphor that helps me wrap my head around it is “EAV brings some aspects of normalization to the database table schema”. In a traditional database, tables have a fixed number of columns 在一个普通的数据库中,表中一般包含多列,像下表所示,

+------------------+
| products         |
+------------------+
| product_id       |
| name             |
| price            |
| etc..            |
+------------------+

+------------+----------------+------------------+---------+
| product_id | name           | price            | etc...  |
+------------+----------------+------------------+---------+
| 1          | Widget A       | 11.34            | etc...  |
+------------+----------------+------------------+---------+
| 2          | Dongle B       | 6.34             | etc...  |
+------------+----------------+------------------+---------+

Every product has a name, every product has a price, etc. 每个产品都有,name,price等等字段。

In an EAV Model, each “entity” (product) being modeled has a different set of attributes. EAV makes a lot of sense for a generic eCommerce solution. A store that sells laptops (which have a CPU speed, color, ram amount, etc) is going to have a different set of needs than a store that sells yarn (yarn has a color, but no CPU speed, etc.). Even within our hypothetical yarn store, some products will have length (balls of yarn), and others will have diameter (knitting needles).在EAV模型中,每个模型化的实体/entity(比如说产品)拥有一系列不同的属性。EAV模型几乎可以提供给电子商务一个通用的数据库解决方案。一个出售电脑(属性:CPU速度,颜色,内存)的网店与一个出售衣服(颜色,尺码,性别)的网店对于商品属性的需要肯定大不相同。即使是在电脑网店中,不同的产品对于属性的要求也有差别,如笔记本电脑(电池),台式电脑(机箱)。

There aren’t many open source or commercial databases that use EAV by default. There are none that are available on a wide variety of web hosting platforms. Because of that, the Magento engineers have built an EAV system out of PHP objects that use MySQL as a data-store. In other words, they’ve built an EAV database system on top of a traditional relational database. 使用EAV模型的数据库程序,在开源及商业软件里都不多。并且多数主机提供商都没有大规模采用这种数据库解决方案。因此,Magento工程师通过MySQL作为底层数据存储,用PHP实现了EAV系统。换句话说,Magento在传统关系数据库上构建了EAV数据库系统。不得不说,这是Magento对于电子商务解决方案领袖地位的体现。

In practice this means any Model that uses an EAV resource has its attributes spread out over a number of MySQL tables. 在实际应用中,这意味着任何使用EAV模型资源的模型,其属性都是分布在MySQL的多个表里(而非像上图中演示的普通数据库那样)。

EAV模型
The above diagram is a rough layout of the database tables Magento consults when it looks up an EAV record for the catalog_product entity. Each individual product has a row in catalog_product_entity. All the available attributes in the entire system (not just for products) are stored in eav_attribute, and the actual attribute values are stored in tables with names like catalog_product_entity_varchar,catalog_product_entity_decimal, catalog_product_entity_etc.. 上图演示了查询一条catalog_product(产品信息)EAV记录时,Magento所需要与数据库表进行交互的一个大概轮廓。每个产品在catalog_product_entity中有一条对应的记录。而整个系统中所有能够使用的属性(颜色,大小,等等,且不限于产品的属性)都保存在eav_attribute表中,注意,该表中只是记录全局使用的属性,并不会记录任何属性值。实际的属性值分散在很多表中,例如catalog_product_entity_attribute_varchar, catalog_product_entity_attribute_decimal, catalog_product_entity_attribute_etc。

Beyond the mental flexibility an EAV system gives you, there’s also the practical benefit of avoiding ALTER TABLE statements. When you add a new attribute for your products, a new row is inserted into eav_attribute. In a traditional relational database/single-table system, you’d need to ALTER the actual database structure, which can be a time consuming/risky proposition for tables with large data-sets. 除了EAV系统提供的超级便利的扩展性之外,对于数据库的完整性也提供了非常具有实际意义的好处。在添加一个新的产品属性时,你只需要在eav_attribute中添加该属性即可。而在传统的关系数据库中,你需要ALTER表结构,增加字段添加新的产品属性,这样很可能会造成不必要的风险

The downside is there’s no one single simple SQL query you can use to get at all your product data. Several single SQL queries or one large join need to be made. 当然,EAV系统也有不够便利的地方,在这个系统中,不跨表的SQL语句几乎无法调用任何拥有的产品信息。大部分操作都需要较为庞大的JOIN连结语句。

在Magento中创建自定义EAV模型

That’s EAV in a nutshell. The rest of this articles is a run-through of what’s needed to create a new EAV Model in Magento. It’s the hairiest thing you’ll read about Magento and it’s something that 95% of people working with the system will never need to do. However, understanding what it takes to build an EAV Model Resource will help you understand what’s going on with the EAV Resources that Magento uses. 上面我们简单介绍了下EAV模型的概念。下文主要讲解如何在Magento中创建一个新的EAV模型。可以想象对于从来没有接触过EAV模型的程序员来说,下文会非常有难度,并且多数程序员很少会用到EAV开发产品。不过,理解EAV模型的创建过程,会帮助你更加深入的理解Magento中所使用的EAV资源。

Because the EAV information is so dense, we’re going to assume you’ve studied up and are already very familiar with Magento’s MVC and grouped class name features. We’ll help you along the way, but training wheels are off. 下文中对于EAV模型的知识点会非常多,假如你对Magento的MVC架构还不够了解,建议你先从头阅读本系列教程。

Weblog, EAV Style
We’re going to create another Model for a weblog post, but this time using an EAV Resource. To start with, setup and create a new module which responds at the the following URL现在,我们开始创建Weblog模块的又一个模型,不过这次使用EAV模型资源。从头开始,创建一个新的模块,Complexworld,写好相关的配置文件,并确保能够在浏览器中访问该模块。

http://example.com/complexworld

If you’re unsure how to do this, be sure you’ve mastered the concepts in the previous tutorials. 再次建议你,如果还不知道如何建立模块,从头开始看这个系列的教程。

Next, we’ll create a new Model named Weblogeav. Remember, it’s the Resource that’s considered EAV. We design and configure our Model the exact same way, so let’s configure a Model similar to one we created in the first ORM article. 能够正常访问该地址之后,接下来创建一个Weblogeav模型。记住,是模型资源区别开普通模型和EAV模型。所以,创建EAV模型的这一过程和创建普通模型没有区别。下面这段代码,和第一次创建blogpost普通模型时差别不大。

<global>
    <!-- ... -->
    <models>
        <!-- ... -->
        <complexworld>
            <class>Magentotutorial_Complexworld_Model</class>
            <resourceModel>complexworld_resource</resourceModel>
        </complexworld>
        <!-- ... -->
    </models>
    <!-- ... -->
</global>

You’ll notice so far there is no difference to setting up a regular Model and flat table resource Model. 细心的你应该注意到了上述代码与创建普通模型代码的区别,在标签中,该模型资源的名字更为复杂了。先不管它,我们继续。

We’ll still need to let Magento know about this resource. Similar to basic Models, EAV Resources are configured in the same node with everything else. 接着,我们需要告诉Magento该模型资源的配置信息。和普通模型类似,EAV资源模型的配置同样是在子节点中。

<global>
    <!-- ... -->
    <models>
        <!-- ... -->
        <complexworld_resource>
            <class>Magentotutorial_Complexworld_Model_Resource</class>
            <entities>
                <eavblogpost>
                    <table>eavblog_posts</table>
                </eavblogpost>
            </entities>
        </complexworld_resource>
        <!-- ... -->
    </models>
    <!-- ... -->
</global>

Again, so far this is setup similar to our regular Model Resource. We provide a <class/> that configures a PHP class, as well as an <entities/> section that will let Magento know the base table for an individual Model we want to create. The <eavblogpost/> tag is the name of the specific Model we want to create, and its inner<table/> tag specifies the base table this Model will use (more on this later). 到目前为止,EAV模型资源的配置与普通模型资源的配置差别还不大。<class />节点提供了一个EAV资源类(继承自Mage_Eav_Model_Entity_Abstract),<entities />内提供给Magento我们想要创建的模型的基本表。<eavblogpost />标签指定了我们将要创建的模型,该标签内部的<table />标签指定了该模型将要使用的基本表(下文有更详细的及时)。

Where Does That File Go?
Until wide adoption of PHP 5.3 and namespaces, one of the trickier (and tedious) parts of Magento will remain remembering how <classname/>s relate to file paths, and then ensuring you create the correctly named directory structure and class files. After configuring any Warning: include(Magentotutorial/Complexworld/Model/Eavblogpost.php) [function.include]: failed to open stream: No such file or directory in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93

In addition to telling us the path where we’ll need to define the new resource class this also serves as a configuration check. If we’d been warned with the following除了告诉我们应该在什么路径创建该资源类之外,如果遇到如下异常,

Warning: include(Mage/Complexworld/Model/Eavblogpost.php) [function.include]: failed to open stream: No such file or directory  in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93

we’d know our Model was misconfigured, as Magento was looking for the Model in code/core/Mage instead ofcode/local/Magentotutorial. 我们就能知道配置文件出错了,因为Magento试图在核心文件中查找该模块,而非local路径中查找。
So, lets create our Model class了解上述内容之后,我们开始创建模型类。在以下路径创建类文件,并添加以下代码。

File: app/code/local/Magentotutorial/Complexworld/Model/Eavblogpost.php:

class Magentotutorial_Complexworld_Model_Eavblogpost extends Mage_Core_Model_Abstract {
    protected function _construct()
    {
        $this->_init('complexworld/eavblogpost');
    }
}

Remember, the Model itself is resource independent. A regular Model and an EAV Model both extend from the same class. It’s the resource that makes them different.
Clear your Magento cache, reload your page, and you should see a new warning.
再一次废话,模型本身是与模型资源独立的。无论是普通模型还是EAV模型都继承自同一个类。模型资源是区别它们的原因。
清空Magento缓存,刷新页面,你会看到一个警告。

Warning: include(Magentotutorial/Complexworld/Model/Resource/Eavblogpost.php)

As expected, we need to create a class for our Model’s resource. Let’s do it!
File: app/code/local/Magentotutorial/Complexworld/Model/Resource/Eavblogpost.php:
很明显,是时候为模型创建模型资源了。在以下路径创建模型资源文件,并添加如下代码。

class Magentotutorial_Complexworld_Model_Resource_Eavblogpost extends Mage_Eav_Model_Entity_Abstract
{
    protected function _construct()
    {
        $resource = Mage::getSingleton('core/resource');
        $this->setType('complexworld_eavblogpost');
        $this->setConnection(
            $resource->getConnection('complexworld_read'),
            $resource->getConnection('complexworld_write')
        );
    }
}

So, already we’re seeing a few differences between a simple Model Resource and an EAV Model Resource. First off, we’re extending the Mage_Eav_Model_Entity_Abstract class. WhileMage_Eav_Model_Entity_Abstract uses the same _construct concept as a regular Model Resource, there’s no _init method. Instead, we need to handle the init ourselves. This means telling the resource what connection-resources it should use, and passing a unique identifier into the setType method of our object. 这里,终于看到普通的模型资源和EAV模型资源的一些区别了。首先,这里扩展的是Mage_Eav_Model_Entity_Abstract类。另外,该类中虽然和普通模型资源一样也使用了_construct()方法,却没有_init()方法。取而代之,我们需要自己处理init()的一些功能。这意味着,我们需要告诉EAV模型资源使用哪一种链接资源,并且传递唯一标示符到对象的setType()方法中。

Another difference in Mage_Eav_Model_Entity_Abstract is _construct is not an abstract method, primarily for reasons of backwards compatibility with older versions of the system. 与普通模型资源类的另外一个不同点是,_construct()不是抽象方法,这主要是为了与Magento老版本之间的兼容问题。

So, with that, let’s clear the Magento cache and reload the page. You should see a new exception which reads清空Magento缓存,刷新页面,看下系统抛出的新异常。

 Invalid entity_type specified: complexworld_eavblogpost
 

Magento is complaining that it can’t find a entity_type named complexworld_eavblogpost. This is the value you set above系统提示其无法找到叫complexworld_eavblogpost的实体类型(entity_type)。这个值是你在setType()方法中传递的参数。
Every entity has a type. Types will, among other things, let the EAV system know which attributes a Model uses, and allow the system to link to tables that store the values for attributes. We’ll need to let Magento know that we’re adding a new entity type. Take a look in the MySQL table named eav_entity_type. 每个实体(Entity)都有一个类型。类型让EAV系统知道模型在使用哪些属性,并允许系统连接存储那些属性值的表。所以这里我们需要让Magento意识到我们添加了一个新的实体类型。首先,我们看下eav_entity_type表。

mysql> select * from eav_entity_type;
*************************** 1. row ***************************
          entity_type_id: 1
        entity_type_code: customer
            entity_model: customer/customer
         attribute_model:
            entity_table: customer/entity
      value_table_prefix:
         entity_id_field:
         is_data_sharing: 1
        data_sharing_key: default
default_attribute_set_id: 1
         increment_model: eav/entity_increment_numeric
     increment_per_store: 0
    increment_pad_length: 8
      increment_pad_char: 0
*************************** 2. row ***************************
          entity_type_id: 2
        entity_type_code: customer_address
            entity_model: customer/customer_address
         attribute_model:
            entity_table: customer/address_entity
      value_table_prefix:
         entity_id_field:
         is_data_sharing: 1
        data_sharing_key: default
default_attribute_set_id: 2
         increment_model:
     increment_per_store: 0
    increment_pad_length: 8
      increment_pad_char: 0

This table contains a list of all the entity_types in the system. The unique identifiercomplexworld_eavblogpost corresponds to the entity_type_code column. 该表包含了系统中可用的所有实体类型(entity_types),该表中的唯一识别complexworld_eavblogpost身份的是entity_type_code列。

Systems and Applications
This illustrates the single most important Magento concept, one that many people struggle to learn.
Consider the computer in front of you. The OS (Mac OS X, Windows, Linux, etc.) is the software system. Your web browser (Firefox, Safari, IE, Opera) is the application. Magento is a system first, and an application second. You build eCommerce applications using the Magneto system. What gets confusing is, there’s a lot of places in Magento where the system code is exposed in a really raw form to the application code. The EAV system configuration living in the same database as your store’s data is an example of this.
If you’re going to get deep into Magento, you need to treat it like it’s an old Type 650 machine. That is to say, it’s the kind of thing you can’t effectively program applications in unless unless you have a deep understanding of the system itself.
这里有必要解释下Magento系统最为重要的一个概念。想象下在你面前的这台电脑,Windows,Linux,Mac等是系统-Systems;你的浏览器(FF,IE,Opera等)是应用-Application。 Magento首先是一个系统,然后才是一个应用程序。你用Magento系统构建电子商务应用。让我们困惑的是,在Magento中有很多的地方系统代码以一个非常原始的形式 向 应用程序代码 被暴露。EAV系统的配置和你商店的数据存放在同一个数据库中就是这样的一个例子。如果你要深入Magento,你需要把它当做它是一个老式650型机器。也就是说,如果你要有效地编写应用程序,你对系统需要由一个深刻的理解。
注:这里其实想说明的是一个非常简单的道理。比如在Linux上做应用软件,你需要对Linux提供的接口非常了解,而且还要对Linux系统的工作原理要非常清楚,这样才能写出高效的应用程序

Creating a Setup Resource 创建启动资源

So, it’s theoretically possible to manually insert the rows you’ll need into the Magento database to get your Model working, but it’s not recommended. Fortunately, Magento provides a specialized Setup Resource that provides a number of helper method that will automatically create the needed records to get the system up and running. 手动创建表并插入相关数据确实没问题,不过推荐使用Magento启动资源(Setup Resource)。Magento的启动资源提供了一系列的助手方法,能够自动创建表及数据,以使系统正常运行。
So, for starters, configure the Setup Resource like you would any other. 首先,我们将启动资源写入配置文件。

<global>
    <!-- ... -->
    <resources>
        <complexworld_setup>
            <setup>
                <module>Magentotutorial_Complexworld</module>
                <class>Magentotutorial_Complexworld_Model_Resource_Setup</class>
            </setup>
        </complexworld_setup>
    </resources>
    <!-- ... -->
</global>

Next, create its class file.
File: app/code/local/Magentotutorial/Complexworld/Model/Resource/Setup.php:

class Magentotutorial_Complexworld_Model_Resource_Setup extends Mage_Eav_Model_Entity_Setup {
}

Take note that we’re extending from Mage_Eav_Model_Entity_Setup rather thanMage_Core_Model_Resource_Setup. 注意这个类继承的是Mage_Eav_Model_Entity_Setup而不是Mage_Core_Model_Resource_Setup。

Finally, we’ll set up our installer script. If you’re not familiar with the naming conventions here, you’ll want to review the setup resource tutorial on Setup Resources. 最后,设置安装脚本。如果你还不是太熟悉这里的命名约定,建议你复习下第六章的内容。在下列路径中创建安装脚本,并写入如下代码,

File: app/code/local/Magentotutorial/Complexworld/sql/complexworld_setup/install-0.1.0.php:

$installer = $this;
throw new Exception("This is an exception to stop the installer from completing");

Clear your Magento Cache, reload you page, and the above exception should be thrown, meaning you’ve correctly configured your Setup Resource. 清空Magneto缓存,重新刷新页面,上述代码中的异常会被系统抛出,这说明启动资源已经被成功配置了。

NOTE: We’ll be building up our install script piece by piece. If you’ve read the previous tutorial, you’ll know you need to remove the setup’s row from the core_resource table and clear your cache to make an installer script re-run. For the remainder of this tutorial, please remember that anytime we add or remove an item from our installer and re-run it, you’ll need to remove this row from the database and clear your Magento cache. Normally you would create this file and run it once, a tutorial is something of an edge case. 注意:我们会逐步完成安装脚本。如果你认真学习了第六章的知识,你会了解到,让系统重新运行安装脚本之前,必须删除core_resource表中的相关模块并清空缓存。在下文中,每修改一次安装脚本,都记得要执行上述步骤。

Adding the Entity Type 添加实体类型
To begin, add the following to your Setup Resource installer script, and then run the script by loading any page (after removing the above exception) 为了添加实体类型,首先需要将下列代码添加到安装脚本中,不要忘记移除上面代码中的异常,然后刷新任意页面。

$installer = $this;
$installer->startSetup();
$installer->addEntityType('complexworld_eavblogpost', array(
    //entity_mode is the URI you'd pass into a Mage::getModel() call
    'entity_model'    => 'complexworld/eavblogpost',

    //table refers to the resource URI complexworld/eavblogpost
    //<complexworld_resource>...<eavblogpost><table>eavblog_posts</table>
    'table'           =>'complexworld/eavblogpost',
));
$installer->endSetup();

We’re calling the addEntityType method on our installer object. This method allows us to pass in the entity type (complexworld_eavblogpost) along with a list of parameters to set its default values. If you’ve run this script, you’ll notice new rows in the eav_attribute_group, eav_attribute_set, and eav_entity_type tables.在这段安装脚本中,我们调用了addEntityType()方法。该方法允许我们传递实体类型及相关参数,并设置它的默认值。运行此脚本之后,在eav_attribute_group,eav_attribute_set和eav_entity_type表中会多出新的记录。

mysql> select * from eav_entity_type where entity_type_code='complexworld_eavblogpost'\G
*************************** 1. row ***************************
             entity_type_id: 31
           entity_type_code: complexworld_eavblogpost
               entity_model: complexworld/eavblogpost
            attribute_model: NULL
               entity_table: complexworld/eavblogpost
         value_table_prefix: NULL
            entity_id_field: NULL
            is_data_sharing: 1
           data_sharing_key: default
   default_attribute_set_id: 0
            increment_model: NULL
        increment_per_store: 0
       increment_pad_length: 8
         increment_pad_char: 0
 additional_attribute_table: NULL
entity_attribute_collection: NULL
1 row in set (0.00 sec)

mysql> select * from eav_attribute_set where entity_type_id=31;
+------------------+----------------+--------------------+------------+
| attribute_set_id | entity_type_id | attribute_set_name | sort_order |
+------------------+----------------+--------------------+------------+
|               65 |             31 | Default            |          1 |
+------------------+----------------+--------------------+------------+
1 row in set (0.00 sec)

mysql> select * from eav_attribute_group where attribute_set_id=65;
+--------------------+------------------+----------------------+------------+------------+
| attribute_group_id | attribute_set_id | attribute_group_name | sort_order | default_id |
+--------------------+------------------+----------------------+------------+------------+
|                213 |               65 | General              |          1 |          1 |
+--------------------+------------------+----------------------+------------+------------+
1 row in set (0.00 sec)

mysql> select * from eav_entity_attribute where attribute_group_id = 213;
+---------------------+----------------+------------------+--------------------+--------------+------------+
| entity_attribute_id | entity_type_id | attribute_set_id | attribute_group_id | attribute_id | sort_order |
+---------------------+----------------+------------------+--------------------+--------------+------------+
|                5246 |             31 |               65 |                213 |          964 |          1 |
|                5247 |             31 |               65 |                213 |          965 |          2 |
+---------------------+----------------+------------------+--------------------+--------------+------------+
2 rows in set (0.00 sec)

mysql> select attribute_id,entity_type_id,attribute_code,is_user_defined,default_value from eav_attribute where attribute_id in(964,965);
+--------------+----------------+----------------+-----------------+---------------+
| attribute_id | entity_type_id | attribute_code | is_user_defined | default_value |
+--------------+----------------+----------------+-----------------+---------------+
|          964 |             31 | content        |               0 | NULL          |
|          965 |             31 | date           |               0 | NULL          |
+--------------+----------------+----------------+-----------------+---------------+
2 rows in set (0.00 sec)

mysql> select attribute_id,entity_type_id,attribute_code,is_user_defined,default_value from eav_attribute where entity_type_id=31;
+--------------+----------------+----------------+-----------------+---------------+
| attribute_id | entity_type_id | attribute_code | is_user_defined | default_value |
+--------------+----------------+----------------+-----------------+---------------+
|          964 |             31 | content        |               0 | NULL          |
|          965 |             31 | date           |               0 | NULL          |
|          963 |             31 | title          |               1 | NULL          |
+--------------+----------------+----------------+-----------------+---------------+
3 rows in set (0.00 sec)

mysql> select entity_id, entity_type_id, attribute_set_id from eavblog_posts limit 2;
+-----------+----------------+------------------+
| entity_id | entity_type_id | attribute_set_id |
+-----------+----------------+------------------+
|         1 |             31 |                0 |
|         2 |             31 |                0 |
+-----------+----------------+------------------+
2 rows in set (0.00 sec)

可以先去理清这些表之间的关系。可以看到,is_user_defined这个字段标记了它是否是用户自定义的,0表示是系统属性,所谓系统属性可以理解为实体必须具备的属性(字段)。如果是系统属性,Magento默认就把它们添加到默认的组中,非系统属性需要手动添加。不过查看eavblog_posts实体表,它的attribute_set_id是0。这个说明它根本不对属性进行分组。但是系统默认还是在属性集合属性组里为它添加了相关的记录,并且把定义为系统属性(不指定默认就是作为系统属性添加)的属性添加到这个默认组中。可以认为这些是垃圾数据。(本人的一家之言)

明白上面的关系其实只要知道,属性组关联到属性,属性组又引用属性集。这样方便分集合,集合中装组,组中装属性(但不是每个EAV模型都需要分集合分组)。

So, with that in place, if we reload our complexworld page, we’ll get a new error.
再次刷新complexworld页面,页面会显示如下错误。

SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘magento.eavblog_posts’ doesnt exist

Creating the Data Tables
So, we’ve told Magento about our new entity type. Next, we need to add the MySQL tables that will be used to store all the entity values, as well as configure the system so it knows about these tables. 现在,我们已经成功让Magento系统意识到新创建的实体类型。然后,我们需要添加一些数据库表用来存储所有的实体值,并继续添加配置文件,让系统认识到这些表的存在。

Our EAV Setup Resource has a method named createEntityTables which will automatically setup the tables we need, as well as add some configuration rows to the system. Let’s add the following line to our setup resource. 如果你曾经观察过Magento核心文件中的安装脚本,你应该能够看到一些用于手动创建EAV模型表的SQL代码。幸运的是,这都已经成为历史了。启动资源拥有一个createEntityTables()方法,该方法能够帮助自动创建我们需要的表,并添加相应的配置文件到系统中。在启动资源中添加如下代码。

$installer->createEntityTables(
    $this->getTable('complexworld/eavblogpost')
);

The createEntityTables method accepts two parameters. The first is the base table name, the second is a list of options. We’re using the Setup Resource’s getTable method to pull the table name from our config. If you’ve been following along, you know this should resolve to the string eavblog_posts. We’ve omitted the second parameter which is an array of options you’ll only need to used it for advanced situations that are beyond the scope of this tutorial. createEntityTables()方法接收两个参数。第一个是表名,第二个是相关配置。我们使用启动资源的getTable()方法从配置文件中获取表名。如果你一直认真学习本系列教程,你能猜到这里相当于字符串eavblog_posts。这里暂且忽略参数二,它一般用于一些高级配置。

After running the above script, you should have the following new tables in your database在成功运行上述安装脚本之后,数据库中会新增下面几张表。

eavblog_posts
eavblog_posts_datetime
eavblog_posts_decimal
eavblog_posts_int
eavblog_posts_text
eavblog_posts_varchar

You’ll also have an additional row in the eav_attribute_set table

mysql> select * from eav_attribute_set order by attribute_set_id DESC LIMIT 1 \G
*************************** 1. row ***************************
  attribute_set_id: 65
    entity_type_id: 37
attribute_set_name: Default
        sort_order: 6

So, let’s go back to our page and reload.

http://example.com/complexworld

Success! You should see no errors or warnings, and and a dumpedMagentotutorial_Complexworld_Model_Eavblogpost — with no data. 这次不会再有任何的警告或者错误了,恭喜你,配置成功!

Adding Attributes 添加属性
The last step we need to take in our Setup Resource is telling Magento what attributes we want our EAV Model to have. This would be equivalent to adding new columns in a single database table setup. Again, the Setup Resource will help us. The method we’re interested in is addAttribute. 差不多到最后一步了,我们需要在启动资源中告诉Magento,新建的模型中需要哪些属性。对于EAV模型来说,添加属性这一步相当于在普通数据表中添加新的字段。这一步中,我们感兴趣的方法只有addAttribute。

The code from the previous section was simply telling Magento about a type of entity that we add to the system. These next bits of code are what will actually add possible attributes for our new type to the system. 实际上,上一小节中,我们只是添加了一个实体类型到Magento系统中。而这里,我们需要做的是,添加一个属于该实体类型的实体到系统中。

We do that with the method addAttribute. When we call addAttribute, Magento will need to do several things to install your entities. 我们使用addAttribute来完成它。 当我们调用addAttribute方法时,Magento需要去做很多事情来安装你的实体。
To start with, we’ll give our Eavblogpost a single attribute named title.

/* ... */
$this->addAttribute('complexworld_eavblogpost', 'title', array(
    //the EAV attribute type, NOT a MySQL varchar
    'type'              => 'varchar',
    'label'             => 'Title',
    'input'             => 'text',
    'class'             => '',
    'backend'           => '',
    'frontend'          => '',
    'source'            => '',
    'required'          => true,
    'user_defined'      => true,
    'default'           => '',
    'unique'            => false,
));
/* ... */

All right, that’s a small pile of code. Let’s break it apart.好吧,这是一小堆的代码。让我们把它拆开。

The first argument to addAttribute is the entity type code. It has to match the code specified when calling addEntityType. It tells Magento which entity we are adding the attribute to, in our example it is ourcomplexworld_eavblogpost entity. To see other available entities that come shipped with Magento, remember you can look into the eav_entity_type table at the entity_type_code column. addAttribute方法的第一参数是实体类型代码。它必须和调用addEntity类型时指定的代码匹配。它告诉Magento往哪儿实体添加属性,在我们的例子中,它是complexworld_eavblogpost实体。要想查看Magento还有哪些可用的实体,你可以查看eav_entity_type表的entity_type_code列。

The second argument to addAttribute is the attribute code. It has to be unique within the given entity.
The third argument is where it get real interesting. This is an array of key value pairs, describing the attribute properties. For the sake of simplicity we’ve chose to define a single attribute, but you could go on to define as many as you’d like, by adding additional addAttribute calls to the setup script. addAttribute方法的第二个参数是属性代码。在给定的实体中,它必须是唯一的(相当于是字段名)。第三个参数才真正有趣。它是一个键值对数组,描述了属性的特性。为了简单起见,我们选择只定义一个属性,但通过在安装脚本中添加额外的addAttribute方法调用,你可以定义多个属性。


Array of Key Value Pairs that Define the Attribute 定义属性的键值对数组

Finally, we have a long list of attribute properties.

//the EAV attribute type, NOT a MySQL varchar
'type'              => 'varchar',
'label'             => 'Title',
'input'             => 'text',
'class'             => '',
'backend'           => '',
'frontend'          => '',
'source'            => '',
'required'          => true,
'user_defined'      => true,
'default'           => '',
'unique'            => false,

Most of these define how Magento would build a backend form element for this attribute, and probably you’ll won’t have to deal with that. That said, the one important property you’ll want to make note of is 大部分的定义是为Magento如何在后端为属性构建表单元素服务的,可能你不需要去管它们。有一个重要的特性你需要注意:

'type' => 'varchar'

This defines the type of the value that the attribute will contain. You’ll recall that we added table for each attribute type 这个定义了属性对应的值的类型(水平表中就是字段数据类型)。你会记得,我们添加的表的每个属性类型

eavblog_posts_datetime
eavblog_posts_decimal
eavblog_posts_int
eavblog_posts_text
eavblog_posts_varchar

While these do not refer to the MySQL column types, (but instead the EAV attribute types), their names (varchar, datetime, etc.) are indicative of the values they’ll hold. 虽然这些没有引用MySQL字段类型(而是EAV属性类型),它们的名字(varchar datetime)表示了它们值的类型。

All of these attribute properties are optional, if we wouldn’t have specified them, Magento would have used a default value. These default values are defined in the _prepareValues method of theMage_Eav_Model_Entity_Setup class (inherited by our setup class). 所有特性都是可选的,如果我们没有指定它们,Magento将使用默认值。这些默认值定义在Mage_Eav_Model_Entity_Setup(被我们的安装类继承)类中的_prepareValues方法中

// Mage_Eav_Model_Entity_Setup
protected function _prepareValues($attr)
{
    $data = array(
        'backend_model'   => $this->_getValue($attr, 'backend'),
        'backend_type'    => $this->_getValue($attr, 'type', 'varchar'),
        'backend_table'   => $this->_getValue($attr, 'table'),
        'frontend_model'  => $this->_getValue($attr, 'frontend'),
        'frontend_input'  => $this->_getValue($attr, 'input', 'text'),
        'frontend_label'  => $this->_getValue($attr, 'label'),
        'frontend_class'  => $this->_getValue($attr, 'frontend_class'),
        'source_model'    => $this->_getValue($attr, 'source'),
        'is_required'     => $this->_getValue($attr, 'required', 1),
        'is_user_defined' => $this->_getValue($attr, 'user_defined', 0),
        'default_value'   => $this->_getValue($attr, 'default'),
        'is_unique'       => $this->_getValue($attr, 'unique', 0),
        'note'            => $this->_getValue($attr, 'note'),
        'is_global'       => $this->_getValue($attr, 'global',
                                 Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL
                             ),
    );

    return $data;
}

The second argument to the method calls to _getValue is the array key from our addAttribute argument array, and the third is the default value. So by default Magento would assume you are adding a varchar attribute with a text input. 在上面代码中_getValue方法的第二个参数就是addAttribute方法第三个参数的数组中的数组键。第三个是默认值。所以,默认Magento将假设你添加了一个使用文本输入的输入框的varchar类型的属性。

Adding the other attributes 添加其他属性
Lets add attributes for the blog post content and the post date. This is what the complete install script looks like. 继续添加blogpost的content和date属性。完全的安装脚本看起来像如下样子:

$installer = $this;
    $installer->startSetup();

    $installer->addEntityType('complexworld_eavblogpost', array(
        //entity_mode is the URI you'd pass into a Mage::getModel() call
        'entity_model'    => 'complexworld/eavblogpost',

        //table refers to the resource URI complexworld/eavblogpost
        //<complexworld_resource>...<eavblogpost><table>eavblog_posts</table>
        'table'           =>'complexworld/eavblogpost',
    ));

    $installer->createEntityTables(
        $this->getTable('complexworld/eavblogpost')
    );

    $this->addAttribute('complexworld_eavblogpost', 'title', array(
        //the EAV attribute type, NOT a MySQL varchar
        'type'              => 'varchar',
        'label'             => 'Title',
        'input'             => 'text',
        'class'             => '',
        'backend'           => '',
        'frontend'          => '',
        'source'            => '',
        'required'          => true,
        'user_defined'      => true,
        'default'           => '',
        'unique'            => false,
    ));
    $this->addAttribute('complexworld_eavblogpost', 'content', array(
        'type'              => 'text',
        'label'             => 'Content',
        'input'             => 'textarea',
    ));
    $this->addAttribute('complexworld_eavblogpost', 'date', array(
        'type'              => 'datetime',
        'label'             => 'Post Date',
        'input'             => 'datetime',
        'required'          => false,
    ));

    $installer->endSetup();

So, now that we have everything in place, lets refresh things one last time to run our installer script. After callingaddAttribute, we should have 一些准备就绪,运行安装脚本。在调用了addAttribute后,我们将有
A new row in eav_entity_type for the complexworld_eavblogpost entity type
A new row in eav_attribute for the title attribute
A new row in eav_attribute for the content attribute
A new row in eav_attribute for the date attribute
A new row in eav_entity_attribute(跟属性组管理 实际保存属性集 属性组ID,实际不只一行,如果设置成系统属性,都会被写入)

Tying it all Together
This is clearly the lamest.blogmodel.ever, but lets try adding some rows and iterating through a collection and get the heck out of here before our heads explode. Add the following two actions to your Index Controller.

public function populateEntriesAction() {
    for ($i=0;$i<10;$i++) {
        $weblog2 = Mage::getModel('complexworld/eavblogpost');
        $weblog2->setTitle('This is a test '.$i);
        $weblog2->setContent('This is test content '.$i);
        $weblog2->setDate(now());
        $weblog2->save();
    }

    echo 'Done';
}

public function showCollectionAction() {
    $weblog2 = Mage::getModel('complexworld/eavblogpost');
    $entries = $weblog2->getCollection()
        ->addAttributeToSelect('title')
        ->addAttributeToSelect('content');
    $entries->load();
    foreach($entries as $entry)
    {
        // var_dump($entry->getData());
        echo '<h2>' . $entry->getTitle() . '</h2>';
        echo '<p>Date: ' . $entry->getDate() . '</p>';
        echo '<p>' . $entry->getContent() . '</p>';
    }
    echo '</br>Done</br>';
}

Let’s populate some entries! Load up the following URL

http://magento.dev/index.php/complexworld/index/populateEntries

If you take a look at your database, you should see 10 new rows in the eavblog_posts table. 查看eavblog_posts表,添加了10行。

mysql> SELECT * FROM eavblog_posts ORDER BY entity_id DESC;
+-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+
| entity_id | entity_type_id | attribute_set_id | increment_id | parent_id | store_id | created_at          | updated_at          | is_active |
+-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+
|        10 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         9 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         8 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         7 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         6 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         5 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         4 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         3 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         2 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
|         1 |             31 |                0 |              |         0 |        0 | 2009-12-06 08:36:41 | 2009-12-06 08:36:41 |         1 |
+-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+

as well as 10 new rows in the eavblog_posts_varchar table.在 eavblog_posts_varchar 表也添加了10行:

mysql> SELECT * FROM eavblog_posts_varchar ORDER BY value_id DESC;
+----------+----------------+--------------+----------+-----------+------------------+
| value_id | entity_type_id | attribute_id | store_id | entity_id | value            |
+----------+----------------+--------------+----------+-----------+------------------+
|       10 |             31 |          933 |        0 |        10 | This is a test 9 |
|        9 |             31 |          933 |        0 |         9 | This is a test 8 |
|        8 |             31 |          933 |        0 |         8 | This is a test 7 |
|        7 |             31 |          933 |        0 |         7 | This is a test 6 |
|        6 |             31 |          933 |        0 |         6 | This is a test 5 |
|        5 |             31 |          933 |        0 |         5 | This is a test 4 |
|        4 |             31 |          933 |        0 |         4 | This is a test 3 |
|        3 |             31 |          933 |        0 |         3 | This is a test 2 |
|        2 |             31 |          933 |        0 |         2 | This is a test 1 |
|        1 |             31 |          933 |        0 |         1 | This is a test 0 |
+----------+----------------+--------------+----------+-----------+------------------+

Finally, let’s pull our Models back out. Load the following URL in your browser 用到资源集合

 http://magento.dev/index.php/complexworld/index/showCollection
 

This should give us a

Warning: include(Magentotutorial/Complexworld/Model/Resource/Eavblogpost/Collection.php) [function.include]: failed to open stream: No such file or directory  in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93

So Close! We didn’t make a class for our collection object! Fortunately, doing so is just as easy as with a regular Model Resource. Add the following file with the following contents 没有建立资源集合类,不过创建它很容易:
File: Magentotutorial/Complexworld/Model/Resource/Eavblogpost/Collection.php:

class Magentotutorial_Complexworld_Model_Resource_Eavblogpost_Collection extends Mage_Eav_Model_Entity_Collection_Abstract
{
    protected function _construct()
    {
        $this->_init('complexworld/eavblogpost');
    }
}

This is just a standard Magento _construct method to initialize the Model. With this in place, reload the page, and we’ll see all the titles and the content outputted. But notice, the date value is missing! 这是一个标准的Magento初始化对象的_construct方法。刷新页面,我们会看到所有的标题和输出的内容。但是注意,日期值丢失了(在查询中没有给出)。

Which Attributes?
Those of you with sharp eyes may have noticed something slightly different about the collection loading. 集合加载有点不一样。

$entries = $weblog2->getCollection()
    ->addAttributeToSelect('title')
    ->addAttributeToSelect('content');

Because querying for EAV data can be SQL intensive, you’ll need to specify which attributes it is you want your Models to fetch for you. This way the system can make only the queries it needs. If you’re willing to suffer the performance consequences, you can use a wild card to grab all the attributes由于EAV数据查询时SQL密集的,你需要指定那些属性是你的模型需要为你取出来的。这样系统就可以只查询那些需要的。如果不用考虑性能问题,你可以使用通配符来获取所有属性。

$entries = $weblog2->getCollection()->addAttributeToSelect('*');

Jumping Off
So, that should give you enough information to be dangerous, or at least enough information so you’re not drowning the next time you’re trying to figure out why the yellow shirts aren’t showing up in your store. There’s still plenty to learn about EAV; here’s a few topics I would have liked to cover in greater detail, and may talk about in future articles
1EAV Attributes: Attributes aren’t limited to datetime, decimal, int, text and varchar. You can create your own class files to model different attributes. This is what the attribute_model entity property is for.
2Collection Filtering: Filtering on EAV collections can get tricky, especially when you’re dealing with the above mentioned non-simple attributes. You need to use the addAttributeToFilter method on your collection before loading.
3The Magento EAV Hierarchy: Magento has taken their basic EAV Model and built up a hierarchy that’s very tied to store functionality, as well as including strategies to reduce the number of queries an EAV Model generates (the concept of a flat Model, for example)
EAV Models are, without a doubt, the most complicated part of the Magento system that an ecommerce web developer will need to deal with. Remember to take deep breaths and that, at the end of the day, its just programming. Everything happens for a concrete reason, you just need to figure out why.

我认为,到这里为止,我们已经学习到了基本的Magento架构及EAV模型的知识。当然,关于EAV还有很多需要学习。下面是关于更高级别,更详细的一些学习主题,大家可以参考一下内容继续深入学习Magento系统。
EAV属性:属性不局限于datetime,decimal,int,text和varchar。你可以创建新类来构建不同的属性。这也是attribute_model为空的原因。
收集过滤(Collection Filtering):EAV模型的收集可能会非常复杂,特别是当你在处理上面提到过的non-simple属性时。在读取数据之前,你需要对收集使用addAttributeToFilter()方法。
Magento EAV 层次:Magento不仅实现了EAV模型,更建立了适应电子商务店铺功能的紧密层级。

永久连接:http://blog.ifeeline.com/396.html
转载请保留出处。

Magento开发文档:Magento Setup Resources

On any fast paced software development project, the task of keeping the development and production databases in sync become a sticky wicket. Magento offers a system to create versioned resource migration scripts that can help your team deal with this often contentious part of the development process. 在所有快节奏开发部署的项目里,保持开发环境与生产环境的数据库同步是一件非常头疼的事情。Magento内置了版本资源移植脚本,能够帮助开发团队在开发过程中很好的解决这个问题。

In the ORM article we created a model for a weblog post. At the time, we ran our CREATE TABLE statements directly against the database. This time, we’ll create a Setup Resource for our module that will create the table for us. We’ll also create an upgrade script for our module that will update an already installed module. The steps we’ll need to take are
1.Add the Setup Resource to our config
2.Create our resource class file
3.Create our installer script
4.Create our upgrade script
在Magento模型与ORM基础一文中,我们为Weblog模块创建了一个模型,因为演示目的,当时手动创建的数据库。这次,我们为模型创建一个启动资源(Setup Resource),让Magento自动创建表。接下来,我们还会创建一个升级安装资源脚本,从而实现模型的自动升级。本节介绍的内容大概分为以下几步。
•添加启动资源配置文件
•创建启动资源类文件
•创建安装脚本
•创建升级脚本

Adding the Setup Resource
So, let’s continue with the weblog module we created last time. In our <global /> section, add the following 继续使用上一篇教程中创建的weblog模块。在<resources />节点中,添加下列配置文件,

<global>
    <!-- ... -->
    <resources>
        <weblog_setup>
            <setup>
                <module>Magentotutorial_Weblog</module>
                <class>Magentotutorial_Weblog_Model_Resource_Setup</class>
            </setup>
        </weblog_setup>
    </resources>
    <!-- ... -->
</global> 

The <weblog_setup> tag will be used to uniquely identify this Setup Resource. It’s encouraged, but not necessary, that you use the modelname_setup naming convention. The <module>Magentotutorial_Weblog</module> tag block should contain the Packagename_Modulename of your module. Finally, <class>Magentotutorial_Weblog_Model_Resource_Setup</class> should contain the name of the class we’ll be creating for our Setup Resource. For basic setup scripts it’s not necessary to create a custom class, but by doing it now you’ll give yourself more flexibility down the line. <weblog_setup>标签用来用来识别该启动资源。 Magento建议该命名使用”模块名_setup”的格式。<module />标签必须包含当前“命名空间_模块名”。<class />指定我们为该模块创建的启动资源类名。对于简单的启动资源脚本,我们不需要创建自定义的类文件,当然应用自定义的启动资源脚本,更灵活,更易于扩展。

After adding the above section to your config, clear your Magento cache and try to load any page of your Magento site. You’ll see an exception something like
添加上述内容到配置文件之后,清空缓存,浏览Magento的任意界面,系统将会抛出如下异常,

Fatal error: Class 'Magentotutorial_Weblog_Model_Resource_Setup' not found in

Magento just tried to instantiate the class you specified in your config, but couldn’t find it. You’ll want to create the following file, with the following contents. Magento试图实例化我们刚刚在配置文件中指定的启动资源类,系统提示无法找到该类。我们在如下路径创建该文件。

File: app/code/local/Magentotutorial/Weblog/Model/Resource/Setup.php
class Magentotutorial_Weblog_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup {
} 

Now, reload any page of your Magento site. The exception should be gone, and your page should load as expected. 完成之后,重新刷新页面,页面就能够正常显示了。到这里,启动资源配置文件初步完成。(注:这个时候虽然没有安装脚本,但是如果此时访问了任意页面,当前这个模块对应的版本号就会写入core_resoure表中,导致如下创建的新安装脚本无法运行,所以要先到表里删除weblog_setup记录)

Creating our Installer Script 创建安装脚本
Next, we’ll want to create our installer script. This is the script that will contain any CREATE TABLE or other SQL code that needs to be run to initialize our module. 接下来,开始创建安装脚本。该脚本会包含CREATE TABLE或其他SQL代码,用来初始化该模块。
First, take a look at your config.xml file首先,回忆下配置文件,

<modules>
    <Magentotutorial_Weblog>
        <version>0.1.0</version>
    </Magentotutorial_Weblog>
</modules> 

This section is required in all config.xml files, and identifies the module as well as the its version number. Your installer script’s name will be based on this version number. The following assumes the current version of your module is 0.1.0. 这部分是所有配置文件必须的,用来识别一个模块的版本号。安装脚本的命名就需要依据此版本号。我们假设当前weblog模块的版本号为0.1.0。
Create the following file at the following location在下列路径创建启动脚本,并暂时写入下列代码。

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/mysql4-install-0.1.0.php
echo 'Running This Upgrade: '.get_class($this)."\n <br /> \n";
die("Exit for now"); 

The weblog_setup portion of the path should match the tag you created in your config.xml file (). The 0.1.0 portion of the filename should match the starting version of your module. Clear your Magento cache and reload any page in your Magento site and you should see something like
上述路径的weblog_setup部分必须匹配在配置文件中设置的标签。0.1.0匹配的是在模块中填写的版本号。清空Magento缓存,访问任意页面,会看到如下内容,

Running This Upgrade: Magentotutorial_Weblog_Model_Resource_Setup
Exit for now
 ... 

Which means your update script ran. Eventually we’ll put our SQL update scripts here, but for now we’re going to concentrate on the setup mechanism itself. Remove the “die” statement from your script so it looks like the following 这说明安装脚本正常运行了。这里为了演示目的,没有加入SQL代码,我们只专注于安装机制本身。现在移除die()语句,保留以下部分。(由于die()终止了PHP执行,这个模块当前并没有写入到core_resource表中)

echo 'Running This Upgrade: '.get_class($this)."\n <br /> \n"; 

Reload your page. You should see your upgrade message displayed at the top of the page. Reload again, and your page should be displayed as normal. 刷新页面,页面会显示升级成功的消息。再次刷新,就会显示正常页面了。

Resource Versions
Magento’s Setup Resources allow you to simply drop your install scripts (and upgrade scripts, which we’ll get to in a bit) onto the server, and have the system automatically run them. This allows you to have all your database migrations scripts stored in the system in a consistent format. Magento的启动资源能够自动运行安装脚本,你只需要将正确的代码放入安装脚本,或者升级脚本当中。由于版本号的原因,你可以保留所有的脚本,便于查看各个版本中数据库结构的变动。
Using your favorite database client, take a look at the the core_setup table使用你最喜欢的数据库客户端,查看core_resource表,

mysql> select * from core_resource;
    +-------------------------+------------+--------------+
    | code                    | version    | data_version |
    +-------------------------+------------+--------------+
    | adminnotification_setup | 1.6.0.0    | 1.6.0.0      |
    | admin_setup             | 1.6.1.0    | 1.6.1.0      |
    | api2_setup              | 1.0.0.0    | 1.0.0.0      |
    | api_setup               | 1.6.0.0    | 1.6.0.0      |
    | backup_setup            | 1.6.0.0    | 1.6.0.0      |
    | bundle_setup            | 1.6.0.0.1  | 1.6.0.0.1    |
    | captcha_setup           | 1.7.0.0.0  | 1.7.0.0.0    |
    | catalogindex_setup      | 1.6.0.0    | 1.6.0.0      |
    | cataloginventory_setup  | 1.6.0.0.2  | 1.6.0.0.2    |
    | catalogrule_setup       | 1.6.0.3    | 1.6.0.3      |
    | catalogsearch_setup     | 1.6.0.0    | 1.6.0.0      |
    | catalog_setup           | 1.6.0.0.14 | 1.6.0.0.14   |
    | checkout_setup          | 1.6.0.0    | 1.6.0.0      |
    | cms_setup               | 1.6.0.0.1  | 1.6.0.0.1    |
    | compiler_setup          | 1.6.0.0    | 1.6.0.0      |
    | contacts_setup          | 1.6.0.0    | 1.6.0.0      |
    | core_setup              | 1.6.0.2    | 1.6.0.2      |
    | cron_setup              | 1.6.0.0    | 1.6.0.0      |
    | customer_setup          | 1.6.2.0.1  | 1.6.2.0.1    |
    | dataflow_setup          | 1.6.0.0    | 1.6.0.0      |
    | directory_setup         | 1.6.0.1    | 1.6.0.1      |
    | downloadable_setup      | 1.6.0.0.2  | 1.6.0.0.2    |
    | eav_setup               | 1.6.0.0    | 1.6.0.0      |
    | giftmessage_setup       | 1.6.0.0    | 1.6.0.0      |
    | googleanalytics_setup   | 0.1.0      | 0.1.0        |
    | googlecheckout_setup    | 1.6.0.1    | 1.6.0.1      |
    | importexport_setup      | 1.6.0.2    | 1.6.0.2      |
    | index_setup             | 1.6.0.0    | 1.6.0.0      |
    | log_setup               | 1.6.0.0    | 1.6.0.0      |
    | moneybookers_setup      | 1.6.0.0    | 1.6.0.0      |
    | newsletter_setup        | 1.6.0.1    | 1.6.0.1      |
    | oauth_setup             | 1.0.0.0    | 1.0.0.0      |
    | paygate_setup           | 1.6.0.0    | 1.6.0.0      |
    | payment_setup           | 1.6.0.0    | 1.6.0.0      |
    | paypaluk_setup          | 1.6.0.0    | 1.6.0.0      |
    | paypal_setup            | 1.6.0.2    | 1.6.0.2      |
    | persistent_setup        | 1.0.0.0    | 1.0.0.0      |
    | poll_setup              | 1.6.0.0    | 1.6.0.0      |
    | productalert_setup      | 1.6.0.0    | 1.6.0.0      |
    | rating_setup            | 1.6.0.0    | 1.6.0.0      |
    | reports_setup           | 1.6.0.0.1  | 1.6.0.0.1    |
    | review_setup            | 1.6.0.0    | 1.6.0.0      |
    | salesrule_setup         | 1.6.0.3    | 1.6.0.3      |
    | sales_setup             | 1.6.0.7    | 1.6.0.7      |
    | sendfriend_setup        | 1.6.0.0    | 1.6.0.0      |
    | shipping_setup          | 1.6.0.0    | 1.6.0.0      |
    | sitemap_setup           | 1.6.0.0    | 1.6.0.0      |
    | tag_setup               | 1.6.0.0    | 1.6.0.0      |
    | tax_setup               | 1.6.0.3    | 1.6.0.3      |
    | usa_setup               | 1.6.0.1    | 1.6.0.1      |
    | weblog_setup            | 0.1.0      | 0.1.0        |
    | weee_setup              | 1.6.0.0    | 1.6.0.0      |
    | widget_setup            | 1.6.0.0    | 1.6.0.0      |
    | wishlist_setup          | 1.6.0.0    | 1.6.0.0      |
    | xmlconnect_setup        | 1.6.0.0    | 1.6.0.0      |
    +-------------------------+------------+--------------+
    55 rows in set (0.00 sec)

This table contains a list of all the installed modules, along with the installed version number. You can see our module near the end该表包含所有已安装的模块及其版本号,可以在底部看到我们创建的weblog模块,

| weblog_setup            | 0.1.0   |

This is how Magento knows not to re-run your script on the second, and on all successive, page loads. The weblog_setup is already installed, so it won’t be updated. If you want to re-run your installer script (useful when you’re developing), just delete the row for your module from this table. Let’s do that now, and actually add the SQL to create our table. So first, run the following SQL. 该表的记录也是Magento知道如何避免重复运行安装、更新脚本的原因。如果在开发阶段,你想让系统重新运行安装脚本,可以从该表中删除相应的模块记录。在上面的演示过程中,系统运行了weblog的安装脚本,当时脚本中还不包含任何SQL内容,这里我们删除weblog_setup记录,并在安装脚本中写入实际的SQL代码,创建weblog的表。首先,运行一下语句删除weblog_setup记录(当然,你可以在phpmyadmin中直接点击删除按钮),

DELETE from core_resource where code = 'weblog_setup';

We’ll also want to drop the table we manually created in the ORM article.

DROP TABLE blog_posts;

Then, add the following code to your setup script. 接着,添加下列SQL代码到启动脚本中,

$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=InnoDB DEFAULT CHARSET=utf8;

    INSERT INTO `{$installer->getTable('weblog/blogpost')}` VALUES (1,'My New Title','This is a blog post','2009-07-01 00:00:00','2009-07-02 23:12:30');
");
$installer->endSetup();

Clear your Magento cache and reload any page in the system. You should have a new blog_posts table with a single row. 清空Magento缓存,访问任意页面,一切正常的话,重新打开数据库,会见到blog_posts表创建成功了。

Anatomy of a Setup Script – Magento启动脚本剖析
So, let’s go over the script line-by-line. First, there’s this (or is that $this?) 一起来看下这个启动脚本,首先,第一行中有个$this。

$installer = $this;

Each installer script is run from the context of a Setup Resource class, the class you created above. That means any reference to $this from within the script will be a reference to an object instantiated from this class. While not necessary, most setup scripts in the core modules will alias $this to a variable called installer, which is what we’ve done here. While not necessary, it is the convention and it’s always best to follow the convention unless you have a good reason for breaking it. 安装脚本都是在启动资源类的上下文中运行的,所以上面的$this表示的就是启动资源类。也就是说,在安装脚本中,任何对于$this的引用都是对于启动资源类对象的引用。在核心模块的启动脚本中,Magento团队都将该对象的引用赋值给$installer变量,当然这并不是必须的,但是从Magento团队的做法来看,这也算是一个最佳实践。

Next, you’ll see our queries are bookended by the following two method calls. 继续解析启动脚本,上面的代码前后调用了两个方法

$installer->startSetup();
//...
$installer->endSetup();

If you take a look at the Mage_Core_Model_Resource_Setup class in app/code/core/Mage/Core/Model/Resource/Setup.php (which your setup class inherits from) you can see that these methods do some basic SQL setup
如果你看过Mage_Core_Model_Resource_Setup类(启动资源类继承自该类),路径位于app/code/core/Mage/Core/Model/Resource/Setup.php,你会发现这些方法进行了一些基础的SQL操作。

public function startSetup()
    {
        $this->getConnection()->startSetup()
        return $this;
    }

    public function endSetup()
    {
        $this->getConnection()->endSetup();
        return $this;
    } 

Look can into Varien_Db_Adapter_Pdo_Mysql to find the real SQL setup executed for MySQL connections in the startSetup() and endSetup() methods.
Finally, there’s the call to the run method 接着再看启动脚本中的最后一块,run()方法。

$installer->run(...);

which accepts a string containing the SQL needed to setup your database table(s). You may specify any number of queries, separated by a semi-colon. You also probably noticed the following
该方法的参数包含创建数据库表的所有SQL语句,就想在命令行中键入的命令一样。可以在这个参数中指定任意数量的SQL语句,只需要用分号隔开每条SQL语句即可。当然,可能你还注意到了下面这个方法,

$installer->getTable('weblog/blogpost')

The getTable method allows you to pass in a Magento Model URI and get its table name. While not necessary, using this method ensures that your script will continue to run, even if someone changes the name of their table in the config file. The Mage_Core_Model_Resource_Setup class contains many useful helper methods like this. The best way to become familiar with everything that’s possible is to study the installer scripts used by the core Magento modules. getTable()方法需要一个Magento模型URI作为参数,返回表名。当然你也可以硬编码为该表的名字,这里之所以使用这个方法,主要是为了确保即使表名更改,也可以正常获取到修改后的表名。Mage_Core_Model_Resource_Setup类包含了许多类似getTable()的非常有用的助手方法。想要学习安装脚本,最好的办法就是看Magento核心代码中模块的SQL升级脚本。


RDBMS Agnostic Scripts

Since version 1.6, Magento (in theory) supports more database backends then only MySQL. Since our setup script contains raw SQL statements, it may not run correctly on a different database system, say MSSQL. For that reason the setup script name is prefixt with the string mysql4- 从1.6版开始,Magento开始支持多种后端数据库而不是仅仅是MySQL(理论上)。由于我们的的安装脚本包含原始的SQL语句,它可能无法正确运行在不同的数据库系统,比如MSSQL。因为这个原因,安装脚本使用mysql4字符串的前缀命名。
In order to make setup scripts cross-database compatible, Magento offers a DDL (Data Definition Language) Table object. Here is an alternative version of our setup script that would run on any supported RDBMS.为了使安装脚本跨数据兼容的,Magento提供了一个DDL(数据定义语言)表对象。这是一个可以运行在任何支持RDBMS的可选安装脚本。

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/mysql4-install-0.1.0.php
$installer = $this;
$installer->startSetup();
$table = $installer->getConnection()->newTable($installer->getTable('weblog/blogpost'))
    ->addColumn('blogpost_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned' => true,
        'nullable' => false,
        'primary' => true,
        'identity' => true,
        ), 'Blogpost ID')
    ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, null, array(
        'nullable' => false,
        ), 'Blogpost Title')
    ->addColumn('post', Varien_Db_Ddl_Table::TYPE_TEXT, null, array(
        'nullable' => true,
        ), 'Blogpost Body')
    ->addColumn('date', Varien_Db_Ddl_Table::TYPE_DATETIME, null, array(
        ), 'Blogpost Date')
    ->addColumn('timestamp', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
        ), 'Timestamp')
    ->setComment('Magentotutorial weblog/blogpost entity table');
$installer->getConnection()->createTable($table);

$installer->endSetup(); 

As you can see, there is no raw SQL in this version of the setup script. So which version should you use? If you want your Modules to run on any RDBMS backend, use the new DDL style upgrade scripts. If you are concerned about backward compatibility, use the raw SQL flavor, that is still supported by Magento 1.6 and 1.7 (and probably will be supported by any 1.x Magento release). 正如你看到的,这个版本的安装脚本没有原始的的SQL。但是你应该使用那个版本呢?如果你希望你的模板可以在任何RDBMS后端运行,请使用新的DDL样式的更新脚本。如果你所关心的是向后兼容性,请使用原始SQL版本,这种方式在1.6和1.7中仍然支持。(也可能是将支持Magento的任何1.x的版本)

Module Upgrades 模块更新
So, that’s how you create a script that will setup your initial database tables, but what if you want to alter the structure of an existing module? Magento’s Setup Resources support a simple versioning scheme that will let you automatically run scripts to upgrade your modules. 上文中已经学习了如何利用Magento启动脚本创建数据库表,如果你想更改已经存在的数据库表的结构,该如何操作呢?Magento的启动资源支持简单的版本支持,能够自动运行脚本升级模块的数据库表结构。

Once Magento runs an installer script for a module, it will never run another installer for that module again (short of manually deleting the reference in the core_resource table). Instead, you’ll need to create an upgrade script. Upgrade scripts are very similar to installer scripts, with a few key differences. Magento模块的安装脚本只会运行一次,并且不会再次为这个模块运行另外一个安装脚本(当然,除非你手动从core_resource表中删除该记录)。所以,如果想更改表结构,可以创建一个升级脚本(upgrade script)。升级脚本和安装脚本非常类似,不同之处也会在下文中一一提到。

To get started, we’ll create a script at the following location, with the following contents首先,在下列路径创建升级脚本并填写如下内容,

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/upgrade-0.1.0-0.2.0.php:
echo 'Testing our upgrade script (upgrade-0.1.0-0.2.0.php) and halting execution to avoid updating the system version number <br />';
die(); 

Upgrade scripts are placed in the same folder as your installer script, but named slightly differently. First, and most obviously, the file name contains the word upgrade. Secondly, you’ll notice there are two version numbers, separated by a “-“. The first (0.1.0) is the module version that we’re upgrading from. The second (0.2.0) is the module version we’re upgrading to. 可以看到,升级脚本与安装脚本都在同样的文件夹中,命名方式稍有不同。最明显的是,升级脚本中包含upgrade。其次,包含两个版本号,用“-”分隔开。第一个版本号(0.1.0)是我们要升级的模块的版本号。第二个(0.2.0)是当前模块要升级到的版本号。
If we cleared our Magento cache and reloaded a page, our script wouldn’t run. We need to update the the version number in our module’s config.xml file to trigger the upgrade清空Magento缓存,访问任意页面,可以看到脚本并不会运行。我们需要更新配置文件中的版本号来促发更新脚本。

<modules>
    <Magentotutorial_Weblog>
        <version>0.2.0</version>
    </Magentotutorial_Weblog>
</modules> 

With the new version number in place, we’ll need to clear our Magento cache and load any page in our Magento site. You should now see output from your upgrade script. 设置了新版本号之后,清空Magento缓存,访问任意页面,就能看到升级脚本输出的内容。(注意这里使用了die()语句组织了脚本继续运行,所以core_resource表中该模块的版本号依然为0.1.0。)
By the way, we also could have names our upgrade script mysql4-upgrade-0.1.0-0.2.0.php. This would indicate our upgrade would contain MySQL specific SQL. 顺便说一下,我么还可以把我们的更新脚本命名为mysql4-upgrade-0.1.0-0.2.0.php。这表明我们的更新脚本将包含针对MySQL的特定SQL.

Before we continue and actually implement the upgrade script, there’s one important piece of behavior you’ll want to be aware of. Create another upgrade file at the following location with the following contents.
在进行实际的更新脚本之前,还有一个很重要的细节需要注意到。在如下路径创建另外一个升级脚本,如下。
File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/upgrade-0.1.0-0.1.5.php:

echo 'Testing our upgrade script (upgrade-0.1.0-0.1.5.php) and NOT halting execution <br />'; 

If you reload a page, you’ll notice you see BOTH messages. When Magento notices the version number of a module has changed, it will run through all the setup scripts needed to bring that version up to date. Although we never really created a version 0.1.5 of the Weblog module, Magento sees the upgrade script, and will attempt to run it. Scripts will be run in order from lowest to highest. If you take a peek at the core_resource table, 访问任意页面,你会发现,页面中会显示两个升级脚本里的内容。Magento在发现某个模块的版本号有变化时,系统会自动运行所有的升级脚本。尽管我们并没有实际创建该模块的0.1.5版本,Magento默认已经读取到了升级脚本,所以就会尝试运行。并且,系统会自动按照版本号从低版本向高版本运行。查看下core_resource表,

mysql> select * from core_resource where code = 'weblog_setup';
+--------------+---------+--------------+
| code         | version | data_version |
+--------------+---------+--------------+
| weblog_setup | 0.1.5   | 0.1.0        |
+--------------+---------+--------------+
1 row in set (0.00 sec)

you’ll notice Magento considers the version number to be 1.5. That’s because we completed executing the 1.0 to 1.5 upgrade, but did not complete execution of the 1.0 to 2.0 upgrade. 你会注意到Magento当前版本为0.1.5(upgrade-0.1.0-0.1.5.php中没有die()或exit()组织脚本运行,所以当前模块的版本号已经被更新为0.1.5)。这是因为完全执行了0.1.0-0.1.5的升级,却没有完成0.1.0-0.2.0的升级(由于die()阻止了PHP运行)。
So, with all that out of the way, writing our actual upgrade script is identical to writing an installer script. Let’s change the 0.1.0-0.2.0 script to read

$installer = $this;
$installer->startSetup();
$installer->getConnection()
    ->changeColumn($installer->getTable('weblog/blogpost'), 'post', 'post', array(
        'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
        'nullable' => false,
        'comment' => 'Blogpost Body'
    )
);
$installer->endSetup();
die("You'll see why this is here in a second"); 

Try refreshing a page in your Magento site and … nothing. The upgrade script didn’t run. The post field in our table still allows null values, and more importantly, the call to die() did not halt execution. Here’s what happened
1.The weblog_setup resource was at version 0.1.0
2.We upgraded our module to version 0.2.0
3.Magento saw the upgraded module, and saw there were two upgrade scripts to run; 0.1.0 to 0.1.5 and 0.1.0 to 0.2.0
4.Magento queued up both scripts to run
5.Magento ran the 0.1.0 to 0.1.5 script
6.The weblog_setup resource is now at version 0.1.5
7.Magento ran the 0.1.0 to 0.2.0 script, execution was halted
8.On the next page load, Magento saw weblog_setup at version 0.1.5 and did not see any upgrade scripts to run since both scripts indicated they should be run from 0.1.0
完成之后访问任意页面。很好,你会发现系统没有提示你运行成功。也就是说升级脚本根本没有运行。post字段还是Null值,更明显的是die()语句也同样没有执行。为什么会这样呢?
•weblog_setup资源的版本是0.1.0
•我们需要升级该模块到0.2.0
•Magento系统查找升级脚本,其中有两个脚本需要运行,0.1.0-0.1.5和0.1.0-0.2.0。
•Magento系统按照版本号,从低到高自动运行上述两个脚本
•首先运行0.1.0-0.1.5
•完成之后,当前weblog_setup资源的版本号是0.1.5
•接着运行0.1.0-0.2.0脚本,执行停止了。
•上步停止的原因是,weblog_setup的版本号已经被升级为0.1.5,并没有发现任何0.1.5-0.2.0这种类似的脚本,因为上述两个脚本全部指示是从0.1.0的版本开始升级。(在Magento 1.7中测试发现,两个脚本都被执行了,对应的weblog_setup记录变成了0.2.0,这里说的对新版本貌似不起作用

设置了三个文件,先执行了0.1.0-0.1.5.php然后是0.1.0-0.2.0.php最后是0.1.5-0.2.0.php
Testing our upgrade script (upgrade-0.1.0-0.1.5.php) and NOT halting execution 
Testing our upgrade script (upgrade-0.1.0-0.2.0.php) and NOT halting execution 
Testing our upgrade script (upgrade-0.1.5-0.2.0.php) and NOT halting execution

The correct way to achieve what we wanted would have been to name our scripts as follows实现这种连续升级的正确方法,应该是以下面的形式命名升级脚本,

upgrade-0.1.0-0.1.5.php #This goes from 0.1.0 to 0.1.5
upgrade-0.1.5-0.2.0.php #This goes 0.1.5 to 0.2.0

Magento is smart enough to run both scripts on a single page load. You can go back in time and give this a try by updating the core_resource table Magento可以自动运行以上两个脚本,可以通过如下语句修改版本号让其回到之前状态:

 UPDATE core_resource SET version = '0.1.0', data_version = '0.1.0' WHERE code = 'weblog_setup';
...
 

永久连接: http://blog.ifeeline.com/390.html
转载请保留出处。

Magento开发文档:Magento模型与ORM基础

The implementation of a “Models Tier” is a huge part of any MVC framework. It represents the data of your application, and most applications are useless without data. Magento Models play an even bigger role, as they typically contain the “Business Logic” that’s often relegated to the Controller or Helper methods in other PHP MVC frameworks. 模型层的实现是任何一个MVC框架的重要组成部分。它用来实现应用程序的数据,并且大部分应用程序在没有数据的情况下都是无用的。Magento模型在系统中扮演了一个更为重要的角色,因为它包含了在其他PHP MVC框架中通常应用于控制器和助手方法中的业务逻辑。

Traditional PHP MVC Models 传统的PHP MVC模型
If the definition of MVC is somewhat fuzzy, the definition of a Model is even fuzzier. Prior to the wide adoption of the MVC pattern by PHP developers, data access was usually raw SQL statements and/or a SQL abstraction layer. Developers would write queries and not think too much about what objects they were modeling. 如果说MVC架构的定义有些模糊,那么模型的定义就更为模糊了。早在MVC模式被PHP开发者普遍接受之前,数据的交互通常是使用原始的SQL语句或者SQL抽象层进行。相对于模型化哪个对象,开发者需要更多考虑编写查询语句。

In this day and age, raw SQL is mostly frowned upon, but many PHP frameworks are still SQL centric. Models will be objects that provide some layer of abstraction, but behind the scenes developers are still writing SQL and/or calling SQL like abstraction methods to read and write-down their data.在当今这个时代,原始的SQL大多是令人难以接受的,但是很多PHP框架仍然是以SQL为中心。模型将提供一些对象抽象层,但是在后端开发者仍然在写SQL和或调用类似SQL的抽闲方法来读取和写入他们的数据。

Other frameworks eschew SQL and take the Object Relational Mapping (ORM) approach. Here, a developer is dealing strictly with Objects. Properties are set, and when a save method is called on the Object, the data is automatically written to the database. Some ORMs will attempt to divine object properties from the database, others require the user to specify them in some way, (usually in an abstract data language such as YAML). One of the most famous and popular implementations of this approach is ActiveRecord. 其它的框架则避开SQL并使用对象关系映射(ORM)的方法。在这里开发人员使用对象进行思考。在对象上设置属性,调用保存方法,数据将自动写入到数据库。有些ORM将试图从数据库中映射值到对象的属性,有些则要求用户以某种方式规定(通常在一个抽象的数据语言,如YAML)。这种方法最有名和最流行的实现是ActiveRecord。

This definition of ORM should suffice for now, but like everything Computer Science these days, the strict definition of ORM has blurred over the years. It’s beyond the scope of this article to settle that dispute, but suffice it say we’re generalizing a bit. ORM的定义对现在来说足够了,但是ORM的严格定义这些年来还是显得模糊。

Magento Models
It should be no surprise that Magento takes the ORM approach. While the Zend Framework SQL abstractions are available, most of your data access will be via the built in Magento Models, and Models you build yourself. It should also come as no surprise that Magento has a highly flexible, highly abstract, concept of what a Model is. 毫无疑问Magento实现了ORM模式。尽管Zend Framework的SQL抽象层能够正常使用,大部分的数据交互将通过内置的Magento模型和用户自己构建的模型完成。Magento系统拥有一个高度灵活,高度抽象的模型层。

>Anatomy of a Magento Model – Magento模型剖析
Most Magento Models can be categorized in one of two ways. There’s a basic, ActiveRecord-like/one-object-one-table Model, and there’s also an Entity Attribute Value (EAV) Model. Each Model also gets a Model Collection. Collections are PHP objects used to hold a number of individual Magento Model instances. The Magento team has implemented the PHP Standard Library interfaces of IteratorAggregate and Countable to allow each Model type to have it’s own collection type. If you’re not familiar with the PHP Standard Library, think of Model Collections as arrays that also have methods attached.大部分Magento模型可以被分为两类。基础的,类似ActiveRecord,或者说是“一个对象一张表”的模型;另外一种是Entity Attribute Value(EAV)模型。每个模型都包含一个模型集合(Model Collection)。集合(Collections)是用来保存多个Magento模型实例的对象。为了允许每种模型类型可以拥有它自己的集合的类型,Magento团队实现了PHP标准库中的IteratorAggregate和Countable接口。如果你不熟悉PHP标准库,可以把模型集合看做成是有方法的数组。

Magento Models don’t contain any code for connecting to the database. Instead, each Model uses a modelResource class, that is used to communicate with the database server (via one read and one write adapter object). By decoupling the logical Model and the code that talks to the database, it’s theoretically possible to write new resource classes for a different database schemas and platforms while keeping your Models themselves untouched.
Magento模型不包含任何连接数据库的代码。取而代之,每个模型使用一个模型资源类,它用来和数据库服务器进行通信(通过读写适配器对象)。通过解耦模型与数据库交互代码,理论上可以通过构建新的资源类来满足任意不同的数据库平台,并且保持模型的完整性。

Creating a Basic Model 创建一个基础的Magento模型
To begin, we’re going to create a basic Magento Model. PHP MVC tradition insists we model a weblog post. The steps we’ll need to take are
1.Crete a new “Weblog” module
2.Create a database table for our Model
3.Add Model information to the config for a Model named Blogpost
4.Add Model Resource information to the config for the Blogpost Model
5.Add a Read Adapter to the config for the Blogpost Model
6.Add a Write Adapter to the config for the Blogpost Model
7.Add a PHP class file for the Blogpost Model
8.Add a PHP class file for the Blogpost Resource Model
9.Instantiate the Model

下面我们开始创建一个基础的Magento模型,我们以简单的weblog博客为例,构建一个模型,总的分为以下几步。
•创建“Weblog”模块
•为模型创建一张表,模型命名为Blogpost
•添加模型信息到配置文件
•添加模型资源信息到配置文件
•添加Read Adapter信息到配置文件
•添加Write Adapter信息到配置文件
•为Blogpost模型添加PHP类文件
•为Blogpost资源模型添加PHP类文件
•初始化模型

>Create a Weblog Module创建 Weblog 模块
You should be an old hat at creating empty modules at this point, so we’ll skip the details and assume you can create an empty module named Weblog. After you’ve done that, we’ll setup a route for an index Action Controller with an action named “testModel”. As always, the following examples assume a Package Name of “Magentotutorial”.
In Magentotutorial/Weblog/etc/config.xml, setup the following route 这个时候,创建一个新的空模块应该没有问题了,这里我们跳过这些细节,假设你已经创建了一个名为Weblog的空模块。完成之后,我们为Index控制器设置路由规则。这里依然假设我们的Package命名为Magentotutorial。
在Magentotutorial/Weblog/etc/config.xml文件中,加入一下路由规则,

<frontend>
    <routers>
        <weblog>
            <use>standard</use>
            <args>
                <module>Magentotutorial_Weblog</module>
                <frontName>weblog</frontName>
            </args>
        </weblog>
    </routers>
</frontend> 

And then add the following Action Controller in

class Magentotutorial_Weblog_IndexController extends Mage_Core_Controller_Front_Action {
    public function testModelAction() {
        echo 'Setup!';
    }
} 

at Magentotutorial/Weblog/controllers/IndexController.php. Clear your Magento cache and load the following URL to ensure everything’s been setup correctly.

http://example.com/weblog/index/testModel

You should see the word “Setup” on a white background.

>Creating the Database Table创建数据表
Magento has a system for automatically creating and changing your database schemas, but for the time being we’ll just manually create a table for our Model.

Using the command-line or your favorite MySQL GUI application, create a table with the following schema
Magento系统能够自动创建和更改数据库模式,这里为了演示,我们先手动为模型创建一个表。使用命令行或你最喜欢的MySQL GUI工具,创建下表,

CREATE TABLE `blog_posts` (
`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`)
)

And then populate it with some data

INSERT INTO `blog_posts` VALUES (1,'My New Title','This is a blog post','2010-07-01 00:00:00','2010-07-02 23:12:30'); 

>The Global Config and Creating The Model 全局配置和创建模型
There are five individual things we need to setup for a Model in our config.
1.Enabling Models in our Module
2.Enabling Model Resources in our Module
3.Add an “entity” table configuration to our Model Resource.
When you instantiate a Model in Magento, you make a call like this
创建Weblog的模型及其配置文件需要以下五步完成,
•在模块中启用模型
•在模块中启用模型资源(Model Resources)
•在模型资源中添加实体“entity”,对于简单的模型来说,该实体即表名

在Magento中实例化一个模型,可以使用如下语法,

$model = Mage::getModel('weblog/blogpost'); 

The first part of the URI you pass into get Model is the Model Group Name. Because it is a good idea to follow conventions, this should be the (lowercase) name of your module, or to be safeguarded agains conflicts use the packagename and modulename (also in lowercase). The second part of the URI is the lowercase version of your Model name.
So, let’s add the following XML to our module’s config.xml.
URI的第一部分叫做模型组名(Model Group Name)。考虑到Magento为类使用__autoload方法,所以该模型组名必须是模块的小写形式。该URI的第二部分是你的模型名的小写形式。
接着,我们开始添加模型的配置代码到模块的config.xml文件中。

<global>
    <!-- ... -->
    <models>
        <weblog>
            <class>Magentotutorial_Weblog_Model</class>
            <!--
            need to create our own resource, cant just
            use core_resource
            -->
            <resourceModel>weblog_resource</resourceModel>
        </weblog>
    </models>
    <!-- ... -->
</global> 

The outer <weblog /> tag is your Group Name, which should match your module name. <class /> is the BASE name all Models in the weblog group will have, also calles Class Prefix. The <resourceModel /> tag indicates which Resource Model that weblog group Models should use. There’s more on this below, but for now be content to know it’s your Group Name, followed by a the literal string “resource”.
So, we’re not done yet, but let’s see what happens if we clear our Magento cache and attempt to instantiate a blogpost Model. In your testModelAction method, use the following code
最外层的<weblog />标签是模型组名,应该匹配模块名。<class />中的值是weblog组中所有的模型都拥有的BASE名。<resourceModel />标签指定weblog组中的模型应该使用哪种模型资源,这里我们先记得它是由模型组名加resource。
现在让我们清理下Magento缓存,尝试下实例化这个blogpost模型。在testModelAction()中,添加如下代码。

public function testModelAction() {
        $blogpost = Mage::getModel('weblog/blogpost');
        echo get_class($blogpost);
    }

and reload your page. You should see an exception that looks something like this (be sure you’ve turned on developer mode). 刷新页面之后,你会看到系统抛出了异常,大概如下,

include(Magentotutorial/Weblog/Model/Blogpost.php) [function.include]: failed to open stream: No such file or directory 

By attempting to retrieve a weblog/blogpost Model, you told Magento to instantiate a class with the name由于在上面那段代码中,试图引用‘weblog/blogpost’模型,Magento会实例化下面这个类,

 Magentotutorial_Weblog_Model_Blogpost

Magento is trying to __autoload include this Model, but can’t find the file. Let’s create it! Create the following class at the following location. Magento使用__autoload加载模型类文件,但是找不到这个文件。下面我们来创建该类,文件路径位于,

 File: app/code/local/Magentotutorial/Weblog/Model/Blogpost.php
class Magentotutorial_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract
{
    protected function _construct()
    {
        $this->_init('weblog/blogpost');
    }
} 

Reload your page, and the exception should be replaced with the name of your class.
All basic Models that interact with the database should extend the Mage_Core_Model_Abstract class. This abstract class forces you to implement a single method named _construct (NOTE: this is not PHP’s constructor __construct). This method should call the class’s _init method with the same identifying URI you’ll be using in the Mage::getModel method call.刷新页面之后,异常就被该类名所取代了。所有的基础模型都必须扩展Mage_Core_Model_Abstract类。这个抽象类强制你必须实现一个名为_construct的方法(注意这个不是PHP中的构造方法)。此方法会调用该类的_init方法,并需要传递在getModel()方法中的参数。

>The Global Config and Resources全局配置和资源
So, we’ve setup our Model. Next, we need to setup our Model Resource. Model Resources contain the code that actually talks to our database. In the last section, we included the following in our config.
到此为止,我们已经成功设置了自定义的模型。接着,我们需要设置它的模型资源。模型资源包含与数据库交互的代码。在上一小节中,我们在配置文件中添加了如下代码,

 <resourceModel>weblog_resource</resourceModel>

The value in <resourceModel /> will be used to instantiate a Model Resource class. Although you’ll never need to call it yourself, when any Model in the weblog group needs to talk to the database, Magento will make the following method call to get the Model resource
在<resourceModel />中的值会实例化一个模型资源类。尽管你从不需要手动调用它,当任何在weblog组中的模型需要与数据库交互时,Magento会调用以下方法获取模型资源,

 Mage::getResourceModel('weblog/blogpost'); 

Again, weblog is the Group Name, and blogpost is the Model. The Mage::getResourceModel method will use the weblog/blogpost URI to inspect the global config and pull out the value in <resourceModel> (in this case, weblog_resource). Then, a model class will be instantiated with the following URI 重申一次,weblog是模型组名,blogpost是模型名。Mage::getResourceModel方法使用weblog/blogpost URI来检查全局配置文件,并获取<resourceModel>中的值(在这里,是weblog_resource)。然后,下列URI地址的模型类将会被实例化。

 weblog_resource/blogpost

So, if you followed that all the way, what this means is, resource models are configured in the same section of the XML config as normal Models. This can be confusing to newcomers and old-hands alike.
So, with that in mind, let’s configure our resource. In our <models> section add
资源模型的配置与模型的配置在XML配置文件中的相同节点,下面我们在<models>节点中添加下列代码,

<global>
    <!-- ... -->
    <models>
        <!-- ... -->
        <weblog_resource>
            <class>Magentotutorial_Weblog_Model_Resource</class>
        </weblog_resource>
    </models>
</global> 

You’re adding the <weblog_resource /> tag, which is the value of the <resourceModel /> tag you just setup. The value of <class /> is the base name that all your resource modes will have, and should be named with the following format这里设置的<weblog_resource />标签,就是刚刚在<resourceModel />标签中设置的值。<class />节点中的值是使用的资源模型的基础命名,它的命名方式大概如下

 Packagename_Modulename_Model_Resource

So, we have a configured resource, let’s try loading up some Model data. Change your action to look like the following现在,我们成功配置了资源模型,来试着从模型数据中读取一些信息吧。稍稍添加一些代码到testModelAction()方法中。

public function testModelAction() {
    $params = $this->getRequest()->getParams();
    $blogpost = Mage::getModel('weblog/blogpost');
    echo("Loading the blogpost with an ID of ".$params['id']);
    $blogpost->load($params['id']);
    $data = $blogpost->getData();
    var_dump($data);
} 

And then load the following URL in your browser (after clearing your Magento cache) 清空Magento缓存,在浏览器中打开如下地址,

http://example.com/weblog/index/testModel/id/1

You should see an exception something like the following

Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost.php) [function.include]: failed to open stream: No such file ....

As you’ve likely intuited, we need to add a resource class for our Model. Every Model has its own resource class. Add the following class at at the following location上面我们提到过,当与数据库交互时,会实例化资源模型类,这里系统提示我们需要为该模型添加一个模型资源类。每个模型都有模型资源类,添加该类到下列路径的文件中,

File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost.php
class Magentotutorial_Weblog_Model_Resource_Blogpost extends Mage_Core_Model_Resource_Db_Abstract{
    protected function _construct()
    {
        $this->_init('weblog/blogpost', 'blogpost_id');
    }
} 

Again, the first parameter of the init method is the URL used to identify the Model. The second parameter is the database field that uniquely identifies any particular column. In most cases, this should be the primary key. Clear your cache, reload, and you should see可以看到,_init方法的第一个参数依旧是模型组名/模型名。参数二是数据库字段,可以是任意唯一字段,大多数情况下,参数二可以指定为主键。清空缓存,刷新页面,页面中会显示如下内容,

Can't retrieve entity config: weblog/blogpost

Another exception! When we use the Model URI weblog/blogpost, we’re telling Magento we want the Model Group weblog, and the blogpost Entity. In the context of simple Models that extend Mage_Core_Model_Resource_Db_Abstract, an entity corresponds to a table. In this case, the table named blog_post that we created above. Let’s add that entity to our XML config.
又一个异常!当我们使用模型URI weblog/blogpost时,我们告诉Magento我们想使用weblog为组名的blogpost实体。在扩展Mage_Core_Model_Resource_Abstract的简单模型中,实体相对应一张表。这里,该表即我们上面创建的blog_post表,添加该实体到配置文件中。

<models>
        <!-- ... --->
        <weblog_resource>
            <class>Magentotutorial_Weblog_Model_Resource</class>
            <entities>
                <blogpost>
                    <table>blog_posts</table>
                </blogpost>
            </entities>
        </weblog_resource>
    </models> 

We’ve added a new <entities /> section to the resource Model section of our config. This, in turn, has a section named after our entity (<blogpost />) that specifies the name of the database table we want to use for this Model.
Clear your Magento cache, cross your fingers, reload the page and …
在配置文件中的resource模型节点中,添加新的<entities />节点。在我们的entity后有了指定数据表的名称节点,它就是我们为这个模型而使用的数据表。
清空Magento缓存,刷新页面,OK…

Loading the blogpost with an ID of 1

array
  'blogpost_id' => string '1' (length=1)
  'title' => string 'My New Title' (length=12)
  'post' => string 'This is a blog post' (length=19)
  'date' => string '2009-07-01 00:00:00' (length=19)
  'timestamp' => string '2009-07-02 16:12:30' (length=19)

Basic Model Operations 基础模型操作
All Magento Models inherit from the the Varien_Object class. This class is part of the Magento system library and not part of any Magento core module. You can find this object at 所有的Magento模型继承自Varien_Object类。这个类是Magento系统类库的一部分而不是Magento核心模块的一部分。你可以在如下路径找到这个对象:

lib/Varien/Object.php

Magento Models store their data in a protected _data property. The Varien_Object class gives us several methods we can use to extract this data. You’ve already seen getData, which will return an array of key/value pairs. This method can also be passed a string key to get a specific field. Magento模型存储它们的数据在一个叫_data的protectd属性中。Varien_Object类提供给我们很多方法,可以使用这些方法读取这些数据。你已经使用过了getData()方法,该方法返回一个包含字段/值的数组。你也可以通过传递字段名作为该方法的参数来获取相应字段的值

$model->getData();
$model->getData('title'); 

There’s also a getOrigData method, which will return the Model data as it was when the object was initially populated, (working with the protected _origData method). 也有一个getOrigData方法,它返回最初填充对象时的数据。

$model->getOrigData();
$model->getOrigData('title'); 

The Varien_Object also implements some special methods via PHP’s magic __call method. You can get, set, unset, or check for the existence of any property using a method that begins with the word get, set, unset or has and is followed by the camel cased name of a property. Varien_Object类通过PHP的魔术方法__call实现了一些特殊的方法。你可以通过get,set,unset以及has加上驼峰命名的字段名的方式,获取、设置、unset及查看任意存在的字段值。

$model->getBlogpostId();
$model->setBlogpostId(25);
$model->unsetBlogpostId();
if($model->hasBlogpostId()){...} 

For this reason, you’ll want to name all your database columns with lower case characters and use underscores to separate characters. 正因为如此,你可能会以小写字母及下划线来命名数据库字段。

CRUD, the Magento Way – Magento的CRUD
Magento Models support the basic Create, Read, Update, and Delete functionality of CRUD with load, save, and delete methods. You’ve already seen the load method in action. When passed a single parameter, the load method will return a record whose id field (set in the Model’s resource) matches the passed in value.
Magento模型通过load(),sava(),delete()方法,提供基础的Create,Read,Update和Delete功能。在上面的控制器方法中,我们已经使用了load()方法。当传递一个参数到load()方法中,该方法会返回与该参数相对应的id字段(在模型资源中设置)的一条记录。

$blogpost->load(1);

The save method will allow you to both INSERT a new Model into the database, or UPDATE an existing one. Add the following method to your Controller save()方法允许你插入新数据到模型中,或更新已经存在的数据。添加如下代码到控制器中。

public function createNewPostAction() {
    $blogpost = Mage::getModel('weblog/blogpost');
    $blogpost->setTitle('Code Post!');
    $blogpost->setPost('This post was created from code!');
    $blogpost->save();
    echo 'post with ID ' . $blogpost->getId() . ' created';
} 

and then execute your Controller Action by loading the following URL

http://example.com/weblog/index/createNewPost

You should now see an additional saved post in you database table. Next, try the following to edit your post

public function editFirstPostAction() {
    $blogpost = Mage::getModel('weblog/blogpost');
    $blogpost->load(1);
    $blogpost->setTitle("The First post!");
    $blogpost->save();
    echo 'post edited';
} 

And finally, you can delete your post using very similar syntax.

public function deleteFirstPostAction() {
    $blogpost = Mage::getModel('weblog/blogpost');
    $blogpost->load(1);
    $blogpost->delete();
    echo 'post removed';
} 

Model Collections 模型集
So, having a single Model is useful, but sometimes we want to grab list of Models. Rather than returning a simple array of Models, each Magento Model type has a unique collection object associated with it. These objects implement the PHP IteratorAggregate and Countable interfaces, which means they can be passed to the count function, and used in for each constructs.
We’ll cover Collections in full in a later article, but for now let’s look at basic setup and usage. Add the following action method to your Controller, and load it in your browser.
对于单独一个模型的操作固然很有用,但是多数时候,我们会同时操作多个模型。比返回多个模型的一个多维嵌套数组更好的是,在Magento中,每个模型类型都有一个唯一的集合对象与其关联。这些对象实现了PHP IteratorAggregate和Countable接口,这意味着它们可以被传递到count函数,并使用for each结构循环出数据。
我们将在后面具体介绍Magento的收集机制,现在我们先简要介绍下它的设置和使用。添加如下代码到控制器中,然后再浏览器中访问该地址。

public function showAllBlogPostsAction() {
    $posts = Mage::getModel('weblog/blogpost')->getCollection();
    foreach($posts as $blogpost){
        echo '<h3>'.$blogpost->getTitle().'</h3>';
        echo nl2br($blogpost->getPost());
    }
} 

Load the action URL,

http://example.com/weblog/index/showAllBlogPosts

and you should see a (by now) familiar exception.

Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php) [function.include]: failed to open stream

You’re not surprised, are you? We need to add a PHP class file that defines our Blogpost collection. Every Model has a protected property named _resourceCollectionName that contains a URI that’s used to identify our collection.
看下上面的PHP代码,你就应该对系统抛出异常不会感到太惊讶了吧?我们需要添加一个类来定义Blogpost的模型集合。每个模型资源拥有一个_resourceCollectionName保护属性,它包含了用来识别集合的URI。

protected '_resourceCollectionName' => string 'weblog/blogpost_collection' 

By default, this is the same URI that’s used to identify our Resource Model, with the string “_collection” appended to the end. Magento considers Collections part of the Resource, so this URI is converted into the class name默认的,该URI也用来识别模型资源,以字符串”_collection”结尾。Magento将集合归为模型资源的一部分,所以该URI转换为类名之后如下,

Magentotutorial_Weblog_Model_Resource_Blogpost_Collection

Add the following PHP class at the following location

File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php
 
class Magentotutorial_Weblog_Model_Resource_Blogpost_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract {
    protected function _construct()
    {
            $this->_init('weblog/blogpost');
    }
} 

Just as with our other classes, we need to init our Collection with the Model URI. (weblog/blogpost). Rerun your Controller Action, and you should see your post information. 和其他类一样,我们需要使用该模型的URI(weblog/blogpsot)来_init模型集合。

后注:
资源类以前一般这样命名:Magentotutorial_Weblog_Model_Mysql4_Blogpost,那是因为它继承自Mage_Core_Model_Mysql4_Abstract,为了保持名字对应关系。但是Mysql4这个名字既丑陋也莫名其妙。在新的Magento版本中作出了修改,跟资源相关的都放入了Resource中,这样原来的Mage_Core_Model_Mysql4_Abstract就对应了新的Mage_Core_Model_Resource_Db_Abstract类,所以我们模型的资源类也放入到模型文件夹的Resoure文件夹中保持统一(Magentotutorial_Weblog_Model_Resource_Blogpost),鄙弃mysql4这样丑陋的名字。

最后附上本文中我个人的学习用例:Magento_Model_ORM

永久连接:http://blog.ifeeline.com/383.html
转载务必保留出处。

Magento开发文档:Magento布局 块和模板

原本连接:http://www.magentocommerce.com/knowledge-base/entry/magento-for-dev-part-4-magento-layouts-blocks-and-templates

这里采用英文后对应中文方法展示。由于水平问题,错误难免。请知晓。

Developers new to Magento are often confused by the Layout and View system. 首次使用Magento的开发者常对它布局和视图系统感到困惑。This article will take a look at Magento’s Layout/Block approach, and show you how it fits into Magento MVC worldview.本文将讨论Magento的布局/块的方法,并向你展示如何将其融入Magento MVC的中。

Unlike many popular MVC systems, Magento’s Action Controller does not pass a data object to the view or set properties on the view object (with a few exceptions). 与许多流行的MVC系统不同,Magento动作控制器不传递一个数据对象到视图中,或者在视图对象上设置属性(只有少数例外) Instead, the View component directly references system models to get the information it needs for display. 相反,视图组件直接引用系统模型,以得到需要显示的信息。

One consequence of this design decision is that the View has been separated into Blocks and Templates. 这种设计的决策的后果之一是视图被分隔成块和模板。 Blocks are PHP objects, Templates are “raw” PHP files (with a .phtml extension) that contain a mix of HTML and PHP (where PHP is used as a templating language).块是PHP对象,模板是原始的PHP文件(扩展名为.phtml),其中混合了HTML和PHP(在块和模板中PHP作为模板语言)。 Each Block is tied to a single Template file. Inside a phtml file, PHP’s $this keyword will contain a reference to the Template’s Block object.每个块连接到一个单一的模板文件。在一个.phtml文件中,PHP的$this关键字将包含一个对模板的块对象的引用。


A quick example 一个快速的例子

Take a look at the default product Template at the file at查看一下产品的默认模板文件

app/design/frontend/base/default/template/catalog/product/list.phtml

You’ll see the following PHP template code.将看到一下PHP模板代码。

 <?php $_productCollection=$this->getLoadedProductCollection() ?>
<?php if(!$_productCollection->count()): ?> <div class="note-msg">
    <?php echo $this->__("There are no products matching the selection.") ?>
</div> <?php else: ?>
... 

The getLoadedProductCollection method can be found in the Template’s Block class, Mage_Catalog_Block_Product_List as shown: getLoadedProductCollection可以在模板块类中找到,Mage_Catalog_Block_Product_List如下所示:

File:app/code/core/Mage/Catalog/Block/Product/List.php
…
public function getLoadedProductCollection(){
	return $this->_getProductCollection();
}
…

The block’s _getProductCollection then instantiates models and reads their data, returning a result to the template. 块的_getProductCollection方法然后去实例化模型并读取数据,返回结果到模板中。

Nesting Blocks嵌套块
The real power of Blocks/Templates come with the getChildHtml method. This allows you to include the contents of a secondary Block/Template inside of a primary Block/Template. 块/模板的真正力量来自getChildHtml()方法。这允许你把第二个块/模板的内容包含到主块/模板中。

Blocks calling Blocks calling Blocks is how the entire HTML layout for your page is created. Take a look at the one column layout Template.
HTML对你创建的页面布局是块调用块再调用块。看一看一列布局模板:

File:app/design/frontend/base/default/template/page/one-column.phtml
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>">
<head>
<?php echo $this->getChildHtml('head') ?>
</head>
<body class="page-popup <?php echo $this->getBodyClass()?$this->getBodyClass():'' ?>">
    <?php echo $this->getChildHtml('content') ?>
    <?php echo $this->getChildHtml('before_body_end') ?>
    <?php echo $this->getAbsoluteFooter() ?>
</body> 

The template itself is only 11 lines long. However, each call to $this->getChildHtml(…) will include and render another Block. These Blocks will, in turn, use getChildHtml to render other Blocks. It’s Blocks all the way down. 模板本身只有11行。但是,每次调用$this->getChildHtml()将包含和渲染另一个块。依次,这些块将使用getChildHtml方法去渲染另外的块。它的块会一直这样进行下去。(It’s Blocks all the way down.)

The Layout 布局
So, Blocks and Templates are all well and good, but you’re probably wondering块和模板都很好,但你可能不知道
1.How do I tell Magento which Blocks I want to use on a page?
如何告诉Magento哪个块是我想在页中使用的块。
2.How do I tell Magento which Block I should start rendering with?
如何告诉Magento哪个块将首先渲染。
3.How do I specify a particular Block in getChildHtml(…)? Those argument strings don’t look like Block names to me. 如何在getChildHtml()方法中指定一个特定的块。那些常数串看起来不像是块的名字。

This is where the Layout Object enters the picture. The Layout Object is an XML object that will define which Blocks are included on a page, and which Block(s) should kick off the rendering process.这些问题引入了布局对象。布局对象是一个XML对象,它将定义哪些块将包含在页面中,并且定义哪些块先开始渲染的过程。

Last time we were echoing content directly from our Action Methods. This time let’s create a simple HTML template for our Hello World module. 上次我们直接在Action方法中直接输出内容。这次让我们创建一个针对我们的Hello World模块的简单的HTML模板。
First, create a file at首先创建一个文件在:

app/design/frontend/base/default/layout/local.xml

with the following contents 添加如下内容

<layout version="0.1.0">
    <default>
        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
    </default>
</layout>

Then, create a file at 然后创建一个文件在:

app/design/frontend/base/default/template/ magentotutorial/helloworld/simple_page.phtml

with the following contents 添加如下内容到该文件:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Hello World</title>
    <style type="text/css">
        body {
            background-color:#f00;
        }
    </style>
</head>
<body>

</body>

最后,每个Action控制器是负责拉开布局的进程。我们需要添加两个方法到Action方法中让其调用。

public function indexAction() {
    //remove our previous echo
    //echo 'Hello Index!';
    $this->loadLayout();
    $this->renderLayout();
} 

Clear your Magento cache and reload your Hello World controller page. You should now see a website with a bright red background and an HTML source that matches what’s in simple_page.phtml.

清空Magento缓存,刷新你的Hellow World控制器页面。

What’s Going On 接下来是什么
So, that’s a lot of voodoo and cryptic incantations. Let’s take a look at what’s going on. 所以,这是一个很大的巫术和神秘的咒语。让我们来看看这是怎么回事。

First, you’ll want to install the Layoutviewer module. This is a module similar to the Configviewer module you built in the Hello World article that will let us peek at some of Magento’s internals. 首先,你要安装Layoutviewer模块,这个模块将让我们可以窥视Magento的内部。

Once you’ve installed the module (similar to how you setup the Configviewer module), go to the following URL 一旦你安装了这个模块,定位到如下URL

http://example.com/helloworld/index/index?showLayout=page

This is the layout xml for your page/request. It’s made up of , and tags. When you call the loadLayout method of your Action Controller, Magento will这个布局xml文件是针对你的页面/请求。它由blcok reference remove标签组成。 当你控制器的Action方法中调用loadlayout方法时,Magento将:
1.Generate this Layout XML 创建这个布局XML
2.Instantiate a Block class for each tag, looking up the class using the tag’s type attribute as a global config path and store it in the internal _blocks array of the layout object, using the tag’s name attribute as the array key. 为每个block标签实例化一个块类,使用标签”type”属性的全局配置路径来寻找类,并且存储在布局对象的_blocks内部数组中。
3.If the tag contains an output attribute, its value is added to the internal _output array of the layout object. 如果block标签包含一个output属性,它的值将添加到布局对象内部的_output数组里。

Then, when you call the renderLayout method in your Action Controller, Magento will iterate over all the Blocks in the _output array, using the value of the output attribute as a callback method. This is always toHtml, and means the starting point for output will be that Block’s Template. 然后,当你在Action控制器中调用renderLayout方法时,Magento将迭代所有的在_output数组中的块,使用output属性的值作为回调方法。这个始终是toHtml,并意味着输出的起点将是块模板。

The following sections will cover how Blocks are instantiated, how this layout file is generated, and finishes up with kicking off the output process.接下的部分将讨论块如何实例化,布局文件如何被创建,并完成输出过程。

Block Instantiation 块实例化
So, within a Layout XML file, a has a “type” that’s actually a Grouped Class Name URI 所以,在一个布局XML文件中,block标签有一个type属性实际上是分组类名URI

<block type=”page/html”
<block type=”page/template_links”

The URI references a location in the (say it with me) global config. The first portion of the URI (in the above examples page) will be used to query the global config to find the page class name. The second portion of the URI (in the two examples above, html and template_links) will be appended to the base class name to create the class name Magento should instantiate. instantiate.URI在全局的配置中引用一个位置。URI的第一部分将用来查询全局配置以达到寻找page的类名(比如上面例子的page)。URI的第二部分将附加到基础类名后用来作为Magento将实例化的类名。

We’ll go through page/html as an example. First, Magento looks for the global config node at 我们使用page/html作为例子。首先,Magento 在全局配置中用如下路径查找节点:

global/blocks/page

Magento全局配置文件寻找类

This gives us our base class prefix Mage_Page_Block. Then, the second part of the URI (html) is appended to the class name to give us our final Block class name Mage_Page_Block_Html. This is the class that will be instantiated.这样我们就获得了基础类的前缀为Mage_Page_Block。然后,URI的第二部分将被附加到Mage_Page_Block后最终得到了Mage_Page_Block_Html这个类名。这就是将被实例化的类。

If we create a block with the same name as an already existing block, the new block instance will replace the original instance. This is what we’ve done in our local.xml file from above.如果我们创建一个块跟已经存在的块的名字一样,新块的实例将替换原始的实例。这就是在上面的我们的local.xml文件中完成的事情。

<layout version="0.1.0">
    <default>
        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
    </default>
</layout>

The Block named root has been replaced with our Block, which points at a different phtml Template file. 名字为root的块被我们的块替换,我们的块指出了一个不同的phtml模板文件。

Using references 使用引用
will hook all contained XML declarations into an existing block with the specified name. Contained nodes will be assigned as child blocks to the referenced parent block.
refernce节点通过name属性指定名字把所有包含XML声明勾入到一个已经存在的块中。它包含的block节点将被作为子块分配到引用的父块中。

<layout version="0.1.0">
    <default>
        <reference name="root">
            <!-- ... another sub block ... -->
            <block type="page/someothertype" name="some.other.block.name" template="path/to/some/other/template" />
        </reference>
    </default>
</layout>

(使用reference节点的name属性引用root块,reference节点内的block块将作为被引用的root块的子块。上面那段话就像说明这个意思)

Even though the root block is declared in a separate layout XML file, the new block is added as a child block. Magento initially creates a page/html Block named root. Then, when it later encounters the reference with the same name (root), it will assign the new block some.other.block.name as a child of the root block. 尽管root块被声明在独立的XML布局文件中,新的块还是作为子块被添加进来了。Magento首先创建一个page/html类型的块并命名为root。然后,当之后遇到使用相同名字(root)的引用时,它会将新块some.other.block.name作为root块的子块。

How Layout Files are Generated 布局文件是如何产生的
So, we have a slightly better understanding of what’s going on with the Layout XML, but where is this XML file coming from? To answer that question, we need to introduce two new concepts; Handles and the Package Layout. 现在我们对布局XML是怎么回事有了一个稍微好一点的理解,但是XML文件从什么地方来?为了回答这个问题,我们需要介绍两个新概念;句柄 和 包布局。

>Handles 句柄
Each page request in Magento will generate several unique Handles. The Layoutview module can show you these Handles by using a URL something like 在Magento中对每个页的请求将产生很多唯一的句柄。Layoutview模块可以展示这些句柄通过访问类似如下的URL

http://example.com/helloworld/index/index?showLayout=handles

You should see a list similar to the following (depending on your configuration) 将看到类似如下的输出
1.default
2.STORE_bare_us
3.THEME_frontend_default_default
4.helloworld_index_index
5.customer_logged_out

Each of these is a Handle. Handles are set in a variety of places within the Magento system. The two we want to pay attention to are default and helloworld_index_index. The default Handle is present in every request into the Magento system. The helloworld_index_index Handle is created by combining the route name (helloworld), Action Controller name (index), and Action Controller Action Method (index) into a single string. This means each possible method on an Action Controller has a Handle associated with it. 列表中的每个都是句柄。在Magento系统中句柄在不同的地方设置。default和helloworld_index_index是我们要注意的两个句柄。在Magento系统中,default句柄在每次请求中都被呈现。helloworld_index_index句柄是通过联合路由名称(helloworld)、动作控制器(index)和Action方法合并而成的一个字符串。这意味着在控制器中的每个方法都有一个句柄和它关联。

>Package Layout 包布局
You can think of the Package Layout similar to the global config. It’s a large XML file that contains every possible layout configuration for a particular Magento install. Let’s take a look at it using the Layoutview module 你能想到的包布局类似全局配置。它是一个巨大的XML文件,包含了所有布局配置。

http://example.com/helloworld/index/index?showLayout=package
http://example.com/helloworld/index/index?showLayout=package&showLayoutFormat=text

You should see a very large XML file. This is the Package Layout. This XML file is created by combining the contents of all the XML layout files for the current theme (or package). For the default install, this is at
你将看到一个非常大的XML文件。这个就是包布局。这个XML文件是联合了当前theme(或package)的所有XML布局文件的内容而创建的。对于默认的安装,它在:

app/design/frontend/base/default/layout/

Behind the scenes there are <frontend><layout><updates /> and <adminhtml><layout><updates /> sections of the global config that contains nodes with all the file names to load for the respective area. <frontend><layout><updates />和<adminhtml><layout><updates /> Once the files listed in the config have been combined, Magento will merge in one last xml file, local.xml. This is the file where you’re able to add your customizations to your Magento install.一旦在配置中的文件完成联合,Magento将再合并最后一个xml文件,local.xml。这个文件可以让定制你的Magento。

Combining Handles and The Package Layout 联合句柄和包布局
So, if you look at the Package Layout, you’ll see some familiar tags such as and , but they’re all surrounded by tags that look like 如果你查看Package布局,你会看到一些类似的标签,比如block 和 reference,但是他们都被标签包围

<default />
<catalogsearch_advanced_index />
etc... 

These are all Handle tags. The Layout for an individual request is generated by grabbing all the sections of the Package Layout that match any Handles for the request. So, in our example above, our layout is being generated by grabbing tags from the following sections
这些是句柄标签。对于独立的请求,布局是 通过 分离 Package布局中对请求匹配的任意句柄的所有部分而生成的。所以,在我们上面的例子中,我们的布局的产生是通过分离来自如下部分的标签:(从如下获取布局)

<default />
<STORE_bare_us />
<THEME_frontend_default_default />
<helloworld_index_index />
<customer_logged_out /> 

There’s one additional tag you’ll need to be aware of in the Package Layout. The tag allows you to include another Handle’s tags. For example
在Package布局中有另外一个标签你需要注意。update节点允许你去包含另外一个句柄标签。例如:

<customer_account_index>
    <!-- ... -->
    <update handle="customer_account"/>
    <!-- ... -->
</customer_account_index> 

Is saying that requests with a customer_account_index Handle should include s from the Handle. 就是说当前请求customer_account_index句柄时将包含来自customer_account句柄的blocks节点。

Applying What We’ve Learned 应用我们所学的
OK, that’s a lot of theory. Lets get back to what we did earlier. Knowing what we know now, adding让我们回到我们之前。

<layout version="0.1.0">
    <default>
        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
    </default>
</layout>

to local.xml means we’ve overridden the “root” tag. with a different Block. By placing this in the Handle we’ve ensured that this override will happen for every page request in the system. That’s probably not what we want.
添加这些内容到local.xml意味着我们要使用一个不同的块覆盖root标签。通过放置内容到default句柄中,从而确保在系统中对所有的页面请求都有进行覆盖。这可能不是我们想要的。
If you go to any other page in your Magento site, you’ll notice they’re either blank white, or have the same red background that your hello world page does. (都是同样的布局) Let’s change your local.xml file so it only applies to the hello world page. We’ll do this by changing default to use the full action name handle (helloworld_index_index). 让我们修改local.xml文件,使得自定义布局只应用到hello world页面。我们通过使用完全的句柄(helloworld_index_index)来改变默认配置。

<layout version="0.1.0">
    <helloworld_index_index>
        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
    </helloworld_index_index>
</layout>

Clear your Magento cache, and the rest of your pages should be restored. 情况Magento的缓存,其它页面将被恢复到默认样子。
Right now this only applies to our index Action Method. Let’s add it to the goodbye Action Method as well. In your Action Controller, modify the goodbye action so it looks like 现在这个只应用到了index方法。让我们也添加到goodby方法。在控制器中,按照如下修改goodbye方法

public function goodbyeAction() {
    $this->loadLayout();
    $this->renderLayout();
} 

If you load up the following URL, you’ll notice you’re still getting the default Magento layout. 这个时候如果访问如下URL,你将注意到你还是看到Mgento的默认布局。

http://example.com/helloworld/index/goodbye

We need to add a Handle for the full action name (helloworld_index_goodbye) to our local.xml file. Rather than specify a new , lets use the update tag to include the helloworld_index_index Handle. 我们需要在local.xml文件中添加一个完整名的句柄(helloworld_index_goodbye)。跟指定一个新的块节点相比,让我们使用update标签来包含helloworld_index_index句柄。

<layout version="0.1.0">
    <!-- ... -->
    <helloworld_index_goodbye>
        <update handle="helloworld_index_index" />
    </helloworld_index_goodbye>
</layout>

Loading the following pages (after clearing your Magento cache) should now produce identical results. 加载如下的页面(在清除了Magento的缓存后)

http://example.com/helloworld/index/index
http://example.com/helloworld/index/goodbye


Starting Output and getChildHtml 开始输出和使用getChildHtml

In a standard configuration, output starts on the Block named root (because it has an output attribute). We’ve overridden root’s Template with our own在标准的配置中,开始输出是从名字为root的块开始的(因为它有output属性)。我们使用了如下我们自己的模板覆盖了root的模板:

template="magentotutorial/helloworld/simple_page.phtml"

Templates are referenced from the root folder of the current theme. In this case, that’s 被引用的模板来自当前theme的root目录。在我们的例子里是:

app/design/frontend/base/default

so we need to drill down to our custom page. Most Magento Templates are stored in
所以我们需要进入到我们自定义的页面。大部分的Magento模板被存储在:

app/design/frontend/base/default/templates

Combining this gives us the full path 网站路径如下:

app/design/frontend/base/default/templates/magentotutorial/helloworld/simple_page.phtml

Adding Content Blocks 添加内容块
A simple red page is pretty boring. Let’s add some content to this page. Change your Handle in local.xml so it looks like the following一个简单的红色页非常无聊。让我们添加一些内容到这个页面中。修改local.xml文件中的helloworld_index_index句柄,让其看起来像如下这样:

<helloworld_index_index>
    <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml">
        <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
    </block>
</helloworld_index_index> 

We’re adding a new Block nested within our root. This is a Block that’s distributed with Magento, and will display a customer registration form. By nesting this Block within our root Block, we’ve made it available to be pulled into our simple_page.html Template. Next, we’ll use the Block’s getChildHtml method in our simple_page.phtml file. Edit simple_page.html so it looks like this 我们添加了一个新块嵌套到我们的root中。这个块是Magento提供的,它会显示客户注册的表单。通过嵌套这个块到我们的root块,我们已经使得它可以在simple_page.html模板中被拉出来。接下来,我们将在simple_page.html中使用块的getChildHtml方法。编辑simple_page.html让其看起来像如下这样:

<body>
    <?php echo $this->getChildHtml('customer_form_register'); ?>
</body>

Clear your Magento cache and reload the page and you should see the customer registration form on your red background. Magento also has a Block named top.links. Let’s try including that. Change your simple_page.html file so it reads清空Magento缓存,刷新页面你将可以看到在红色的背景上的客户注册表单。Magento还有一个叫top.links的块。让我们试图包含它。修改simple_page.html文件:

<body>
    <h1>Links</h1>
    <?php echo $this->getChildHtml('top.links'); ?>
</body> 

When you reload the page, you’ll notice that your <h1>Links</h1> title is rendering, but nothing is rendering for top.links. That’s because we didn’t add it to local.xml. The getChildHtml method can only include Blocks that are specified as sub-Blocks in the Layout. This allows Magento to only instantiate the Blocks it needs, and also allows you to set difference Templates for Blocks based on context. 当你刷新页面时,你会注意到h1标签内容已经输出,但是没有看到top.links的输出。那是因为我们没有添加配置到local.xml。getChildHtml方法只能包含那些在布局中指定为子块的块。这允许Magento只去实例化那些它需要的块,并且也允许你基于内容为块设置不同的模板。

Time for action
There is one more important concept to cover before we wrap up this lesson, and that is the tag. Using the tag enables us to call public PHP methods of the block classes. So instead of changing the template of the root block by replacing the block instance with our own, we can use a call to setTemplate instead.还有一个action标签比较重要。使用action标签可以让我们调用块类的公共方法。所以作为使用自己的块实例替换root块的模板的替代方法,我们可以使用setTemplate代替。

<layout version="0.1.0">
    <helloworld_index_index>
        <reference name="root">
            <action method="setTemplate">
                <template>magentotutorial/helloworld/simple_page.phtml</template>
            </action>
            <block type="page/template_links" name="top.links"/>
            <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
        </reference>
    </helloworld_index_index>
</layout> 

This layout XML will first set the template property of the root block, and then will add the two blocks we use as child blocks. Once we clear the cache, the result should look just as before. The benefit of using the is the same block instance is used that was created earlier, and all other parent/child associations still exist. For that reason this is a more upgrade proof way of implementing our changes. 布局XML将首先设置root块模板属性,然后将添加两个子块。清空缓存后,结果和前面的一样。使用action的好处是可以使用在之前创建的相同的块实例,并且所有其他的父/子块关联到它的还会存在(保存层次结构,不能跨越调用)。

All arguments to the action’s method need to be wrapped in an individual child node of the tag. The name of that node doesn’t matter, only the order of the nodes. We could have written the action node from the previous example as follows with the same effect. 所有传递给Action方法的参数需要使用在action标签中使用独立的子节点包围。节点名字无所谓,但是要注意节点顺序。我们可以使用如下代码改写之前的例子中的代码,它效果是一样的。

<action method="setTemplate">
    <some_new_template>magentotutorial/helloworld/simple_page.phtml</some_new_template>
</action>

This is just to illustrate that the action’s argument node names are arbitrary. 这只是说明action节点中的参数节点的名字是任意的。

永久连接:http://blog.ifeeline.com/369.html
转载请保留以上出处。

Magento开发文档:Magento控制器

Magento团队创建了一个更为抽象的MVC模式,大概的运行过程是:
>URL地址首先被一个PHP文件解析
>该PHP文件会根据解析情况实例化一个Magento应用
>这个Magento应用会实例化一个前端控制器对象
>接着,前端控制器实例化路由对象
>路由对象检查请求的URL地址,并作出相应的匹配
>如果匹配成功,相应的控制器和动作会被分发
>该控制器会被实例化,并且与动作同名的方法会被调用
>被调用的方法根据请求的类型,对相应的模型调用相应的方法以获取数据
>结束方法调用之后,控制器会实例化布局对象
>根据请求中包含的变量及系统属性(通常叫做句柄),布局对象会为该请求创建一系列的Block对象
>布局还会在相应的Block对象中调用输出方法,开始套嵌输出(Blocks之间的套嵌)
>每个Block都有相关联的模板文件,Blocks包含PHP逻辑代码,模板文件则负责生成HTML文件
>Block从模型中获取相关数据,换句话说,控制器并不用来传递数据到视图中

这里直接创建一个模块(具体步骤跳过),如下配置文件:
Magento控制器路由

“frontend”区(Area)是Magento的前端应用。“admin”区(Area)是后端应用。“install”区(Area)是用来安装Magento的应用。

routers标签通常会包括关于路由规则的配置信息,有时候又会包含实际的路由对象的配置信息。

frontName标签记录了对应模块在前端展示时使用的名字,一般保持和模块名一致。比如:

http://example.com/frontName/actionControllerName/actionMethod/

frontName标签对应了这个格式里面出现的名字。它和所谓的前端控制器毫无关系。

helloworld标签必须是当前模块的小写形式。创建模式是Helloworld,所以该标签是helloworld。

module标签中必须是模块的全名。该配置让系统能够正确定位到控制器文件。

创建动作控制器
完成配置之后,在以下路径创建该类:

app/code/local/Magentotutorial/Helloworld/controllers/IndexConttoller.php

class Magentotutorial_Helloworld_IndexController extends Mage_Core_Controller_Front_Action {
    public function indexAction() {
        echo 'Hello Index';
    }
}
//清空缓存 访问如下地址之一
http://example.com/helloworld/index/index
http://example.com/helloworld/index
http://example.com/helloworld/

控制器应该放在模块的controllers文件夹中,系统会自动在这个路径中寻找控制器。

动作控制器命名规则:
>以配置文件module标签中的值起头(Magentotutorial_Helloworld)
>紧接着一个下划线(Magentotutorial_Helloworld_)
>再接着是该控制器的名字(Magentotutorial_Helloworld_Index)
>最后,加上Controller(Magentotutorial_Helloworld_IndexController)

所有的Magento控制器都是继承自Mage_Core_Controller_Front_Action类。

如下URL:

http://example.com/helloworld/index/index

URI中的helloworld是frontName,后面的两个index分别是调用的控制器已经方法,即嗲用helloworld模块中的IndexController控制器中的indexAction方法。如果URL地址中缺少控制器以及方法部分,Magento默认使用index。

IndexController继承自Mage_Core_Controller_Front_Action类,有很多方法可以直接使用。比如,除上述URI中提到的三部分之外,其它部分会自动传给一个键值对数组。

public function paramAction(){
	foreach($this->getRequest()->getParams() as $key => $value){
		echo “Key ”.$key.”-- Value”.$value;
        }
}

永久连接:http://blog.ifeeline.com/348.html

Magento开发文档:Magento配置

设置Magento模块的目录结构
在这里,我们使用”Magentotutorial”。那么首先,要创建模块,我们需要创建如下所示的目录结构,

app/code/local/Magentotutorial/Configviewer/Block
app/code/local/Magentotutorial/Configviewer/controllers
app/code/local/Magentotutorial/Configviewer/etc
app/code/local/Magentotutorial/Configviewer/Helper
app/code/local/Magentotutorial/Configviewer/Model
app/code/local/Magentotutorial/Configviewer/sql

一个模块并不一定需要上述所有的目录,但是可以把它们先创建好。然后需要创建两个配置文件,一个是位于上述路径etc目录中的config.xml:

app/code/local/Magentotutorial/Configviewer/etc/config.xml

第二个位于下面的路径:

app/etc/modules/Magentotutorial_Configviewer.xml

模块的配置文件config.xml文件会包含下面的代码:
Magento模块配置文件

第二个文件Magentotutorial_Configviewer.xml
Magento模块全局配置文件

清空Magento缓存,访问System-Configration-Advanced,点击Disable modules output,查看是否存在此模块:
Magento查看模块是否启用
建立一个Index控制器

class Vfeelit_Configviewer_IndexController extends Mage_Core_Controller_Front_Action{
	
    public function indexAction()
    {
		header("Content-Type: text/xml");
		echo Mage::app()->getConfig()->getNode()->asNiceXml();
	}
}

访问:learn.magento.com/configviewer/index/index,会看到一个巨大的XML文件。它描述了当前运行的Magento系统的整个状态。能够找到所有模块、模型、类、事件监听者已经存储于Magento系统中的配置。
Magento在全局XML文件中查找
从这个全局文件中,可以搜索出模块的配置合并到了这个全局文件中。

总的来说Magento中所有模块的配置文件都会被解析并包含在这个全局配置文件当中。

比如:

$helper = Mage::helper(‘sales’);

这个静态方法将会:
>在配置文件查找helpers节点
>在helpers节点内查找sales节点
>在sales节点内再查找class节点
>实例化class节点中找到的类

通过在配置文件中查询类名,可以重写Magento核心功能,而不需要改变或添加任何代码到核心库中。

附上这里建立的模块和全局的XML文件输出

永久连接:http://blog.ifeeline.com/338.html