官术网_书友最值得收藏!

Magento Model anatomy

As we learned in the previous chapter, Magento data models are used to manipulate and access the data. The model layer is pided into two fundamental types, simple models and EAV:

  • Simple models: These model implementations are a simple mapping of one object to one table, meaning our object attributes match each field and our table structure.
  • Entity Attribute Value models: This type of models, known as EAV models, are used to describe entities with a dynamic number of attributes.
Note

It is important to clarify that not all Magento models extend or make use of the ORM. Observers are a clear example of simpler model classes that are not mapped to a specific database table or entity.

In addition to that, each Model type is formed by the following layers:

  • Model class: This is where most of our business logic resides. Models are used to manipulate the data, but they don't access it directly.
  • Resource Model class: Resource Models are used to interact with the database on behalf of our models. They are in charge of the actual CRUD operations.
  • Model Collection class: Each Data Model has a collection class. Collections are objects that hold a number of inpidual Magento Model instances.
Note

CRUD stands for the four basic types of database operations, namely create, read, update, and delete.

Magento Models don't contain any logic to communicate with the database; they are database agnostic. Instead, this code resides in the Resource Model layer.

This gives Magento the capacity to support different types of databases and platforms. Although currently, only MySQL is officially supported, it is entirely possible to write a new resource class for a new database without touching any of the Model logic:

Let's experiment now by instantiating a product object and setting some of its properties:

  1. Start the Magento interactive console running under your Magento staging installation root:
    php shell/imc.php
    
  2. Our first step is to create a new product object instance by typing the following code:
    magento> $product = Mage::getModel('catalog/product');
    
  3. We can confirm this is a blank instance of the product class by running the following code:
    magento> echo get_class($product);
    
  4. We should see the following as a successful output:
    magento> Magento_Catalog_Model_Product
    
  5. If we want to know more about the class methods, we can run the following code:
    magento> print_r(get_class_methods($product));
    

This will return an array with all the available methods inside the class. Let's try to run the following code snippet and modify a product's price and name:

$product = Mage::getModel('catalog/product')->load(2);
$name   = $product->getName() . '-TEST';
$price   = $product->getPrice();
$product->setPrice($price + 15);
$product->setName($name);
$product->save();

On the first line of code, we instantiate a specific object and then proceed to retrieve the name attribute from the object. Next, we set the price and name, and finally, we save the object.

If we open our Magento product class Mage_Catalog_Model_Product, the first thing we will notice is that while both getName() and getPrice() are defined inside our class, the setPrice() and setName() functions are not defined anywhere.

However, why, and more importantly how, is Magento magically defining each of the product object setter and getter methods? While getPrice() and getName() are indeed defined, there is no definition for any of the getter and setter methods for product attributes such as color or manufacturer.

It's magic – methods

Well it so happens that the Magento ORM system is indeed magic. To be precise, one of PHP's more powerful features is to implement its getters and setters, the magic __call() method. Magic methods are used inside Magento to set, unset, check, or retrieve data.

When we try to call a method that does not actually exist in our corresponding class, PHP will look into each of the parent classes for a declaration of that method. If it can't find the function on any of the parent classes, it will use its last resort and try to use a __call() method. If found, Magento (or PHP for that matter) will call the magic method, passing the requested method name and its arguments.

Now, the Product model doesn't have a __call() method defined, but it gets one from the Varien_Object that most Magento models inherit from. The inheritance tree for the Mage_Catalog_Model_Product class is as follows:

Tip

Most Magento Models inherit from the Varien_Object class.

Let's take a closer look at the Varien_Object class:

  1. Open the file located in magento_root/lib/Varien/Object.php.
  2. The Varien_Object class not only has a __call() method but also has two deprecated methods, __set() and __get(). Both of these are replaced by the __call() method and are no longer used.
    public function __call($method, $args)
    {   switch (substr($method, 0, 3)) {
          case 'get' ://Varien_Profiler::start('GETTER: '.get_class($this).'::'.$method);
              $key = $this->_underscore(substr($method,3));
              $data = $this->getData($key, isset($args[0]) ? 
              $args[0] : null);
                    
    //Varien_Profiler::stop('GETTER: '.get_class($this).'::'.$method);
              return $data;
    
              case 'set' :
              //Varien_Profiler::start('SETTER: '.get_class($this).'::'.$method);
              $key = $this->_underscore(substr($method,3));
              $result = $this->setData($key, isset($args[0]) ? $args[0] : null);
              //Varien_Profiler::stop('SETTER: '.get_class($this).'::'.$method);
                return $result;
              case 'uns' :
              //Varien_Profiler::start('UNS: '.get_class($this).'::'.$method);
             $key = $this->_underscore(substr($method,3));
             $result = $this->unsetData($key);
             //Varien_Profiler::stop('UNS: '.get_class($this).'::'.$method);
             return $result;
    
             case 'has' :
             //Varien_Profiler::start('HAS: '.get_class($this).'::'.$method);
             $key = $this->_underscore(substr($method,3));
             //Varien_Profiler::stop('HAS: '.get_class($this).'::'.$method);
             return isset($this->_data[$key]);
        }
        throw new Varien_Exception("Invalid method" . get_class($this)."::".$method."(".print_r($args,1).")");
    }

Inside the __call() method, we have a switch that will handle not only getters and setters but also the unset and has functions.

If we start a debugger and follow the calls of our snippet code to the __call() method, we will see that it receives two arguments, the method name (for example setName()) and the arguments from the original call.

Interestingly, Magento tries to match the corresponding method type based on the first three letters of the method being called. This is done with the switch case argument by calling the substring function:

substr($method, 0, 3)

The first thing that is called inside each case is the _underscore() function, which takes as parameter anything after the first three characters in the method name. Following our example, the argument passed will be Name.

The __underscore() function returns a data key. This key is then used by each of the cases to manipulate the data. There are four basic data operations, each is used on the corresponding switch case:

  • setData ($parameters)
  • getData ($parameters)
  • unsetData ($parameters)
  • isset ($parameters)

Each of these functions will interact with the Varien_Object data array and manipulate it accordingly. In most cases, a magic set/get method will be used to interact with our object attributes. Only in a few exceptions, where additional business logic is required, getters and setters will be defined. In our example, this would be getName() and getPrice():

public function getPrice()
{
    if ($this->_calculatePrice || !$this->getData('price')) {
      return $this->getPriceModel()->getPrice($this);
    } else {
      return $this->getData('price');
    }
}

We will not get into details of what the price function is actually doing, but it clearly illustrates that additional logic may be required for certain parts of the models:

public function getName()
{
    return $this->_getData('name');
}

On the other hand, the getName() getter wasn't declared because of the need to implement special logic but by the need to optimize a crucial part of Magento. The Mage_Catalog_Model_Product getName() function, which can potentially be called hundreds of times per page load, is one of the most commonly used functions across all Magento. After all, what kind of e-commerce platform would it be if it was not centered around products?

Frontend and backend will both call the getName() function at one point or another. For example, loading a category page with 24 products. That's 24 separate calls to the getName() function. Having each of these calls look for a getName() method on each of the parent classes and then trying to use the magic __call() method will result in losing precious milliseconds.

Resource Models contain all the database-specific logic and they instantiate specific read-and-write adapters for their corresponding data source. Let's go back to our example of working with products and take a look the product Resource Model located in Mage_Catalog_Model_Resource_Product:

Resource Models come in two different types, Entity and Resource. The latter is a pretty standard one-table/one-model association, while the former is far more complicated.

主站蜘蛛池模板: 新宾| 吉木萨尔县| 万年县| 连平县| 仪征市| 织金县| 荃湾区| 文成县| 那坡县| 屏边| 崇义县| 施甸县| 肇源县| 崇阳县| 祁门县| 诸城市| 涟源市| 东兰县| 郑州市| 含山县| 虎林市| 元朗区| 来凤县| 柏乡县| 浑源县| 长武县| 册亨县| 德州市| 灌南县| 东平县| 图木舒克市| 衡山县| 宁安市| 泸水县| 宾阳县| 井冈山市| 区。| 武定县| 湖南省| 东安县| 鄂伦春自治旗|