Métodos Mágicos


Si abrimos la clase Product de Magento Mage_Catalog_Model_Product, la primera cosa que notaremos es que mientras getName() y getPrice() estan definidos dentro de la clase Mage_Catalog_Model_Product de Magento, las funciones setPrice() y setName() no están definidas en ningun lugar.

Pero ¿por qué es tan importante, cómo es que Magento mágicamente esta definiendo cada uno de los métodos setter y getter del objeto producto? Mientras que getPrice() y getName() son definidos, no existe una definición para ninguno de los métodos get y set de los atributos del producto, como el color o el fabricante.

Bien, sucede que el sistema ORM de Magento en efecto está usando una de los características más potentes de PHP para la implementación de sus getters y setters, el método mágico __call(). Los métodos que son utilizados dentro de Magento se usan para fijar, desactivar, comprobar o recuperar datos.

Cuando tratamos de llamar a un método, que realmente no existe en nuestra clase correspondiente, PHP buscará en cada una de las clases padres la declaración de ese método. Si no puede encontrar la función en alguna de las clases padres, hará uso de su último recurso y tratará de usar un método __call(), y si lo encuentra, Magento (o PHP para el caso) llamará al método mágico, pasando así el nombre del método solicitado y sus argumentos.

Ahora, el modelo Product no tiene un método __call() definido, pero consigue uno de la clase Varien_Object desde el que heredan todos los modelos de Magento. El árbol de herencia de la clase Mage_Catalog_Model_Product se presenta en el siguiente diagrama de flujo:

Echemos un vistazo más de cerca a la clase Varien_Object:

  • Abre el archivo ubicado en magento_raiz/lib/Varien/Object.php.
  • La clase Varien_Object no sólo tiene un método __call(), sino que también tiene dos métodos en desuso, __set() y __get(); estos dos se sustituyen por el método __call() y por lo tanto ya no se utilizan.
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).")");
    }

Dentro del método __call(), tenemos un switch que se encargará no sólo de los getter y setter, sino también de las funciones unset y has .

Si empezamos a depurar y seguimos las llamadas del fragmento de código del método __call(), podemos ver que este recibe dos argumentos: el nombre del método, por ejemplo setNombre() y los argumentos de la llamada original.

Curiosamente, Magento intenta hacer coincidir el tipo del método correspondiente basado en las tres primeras letras del método que es llamado; esto se hace cuando el argumento del switch case llama a la función substrde PHP.

substr($method, 0, 3)

La primera cosa que se llama dentro de cada case es la función _underscore(), que toma como parámetro algo después de los tres primeros caracteres del nombre de método.

La función __underscore() devuelve una llave de datos "data key". Este key es utilizado por cada uno de los casos para manipular los datos. Hay cuatro operaciones básicas de datos, cada uno se utiliza en el switch case correspondiente:

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

Cada una de estas funciones interactúan con la matriz de datos Varien_Object y se manipulará en consecuencia. En la mayoría de los casos, un método mágico set/get será utilizado para interactuar con los atributos de nuestro objeto; sólo en unas pocas excepciones en que se requiera la lógica de negocio adicional, se definirán getters y setters. En nuestro ejemplo, son getName() y getPrice().

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

No vamos a entrar en detalles de lo que la función price realmente está haciendo, pero claramente ilustra que la lógica adicional podría ser requerida por ciertas partes de los modelos.

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

Por otro lado, el getter getName() no fue declarado debido a la necesidad de implementar una lógica especial, pero por la necesidad de optimizar una parte crucial de Magento. La función getName() de Mage_Catalog_Model_Product puede ser potencialmente llamada cientos de veces por la carga de la página y es una de las funciones más utilizadas en todo Magento.

Desde el frontend y el backend se llamará por igual a la función getName() de un momento a otro. Por ejemplo, si se carga una página de categoría con 24 productos, es decir, 24 llamadas separadas a la función getName(), cada una de estas llamadas buscará un método getName() en cada una de las clases padres, y luego, cuando tratamos de utilizar un método mágico __call(), resultará en una pérdida de milisegundos preciosos.

Los modelos de recursos contienen toda la lógica específica de la base de datos, y ellos ejemplifican la lectura específica y escriben adaptadores para su correspondiente fuente de datos. Volvamos a nuestro ejemplo de trabajar con productos y echar un vistazo al modelo de recurso de producto situado en Mage_Catalog_Model_Resource_Product.

Los modelos de recurso son de dos tipos diferentes: Entity y MySQL4. Siendo esta última una asociación bastante estándar de una-tabla/un-modelo, mientras que el primero es mucho más complicado.

 

Añadir nuevo comentario

CAPTCHA
Esta pregunta es para comprobar si usted es un visitante humano y prevenir envíos de spam automatizado.
3 + 2 =
Resuelva este simple problema matemático y escriba la solución; por ejemplo: Para 1+3, escriba 4.