Question

I'm using Zend Framework and implementing Domain Model. I have Models, Mappers and DbTables.

Suppose we should fetch multiple rows from database or fetch form data where we should create multiple models and populate that models from database rows or form.

Should I implement fetching and creation of models in Mapper and then call that method from Controller? Or I should implement this in Model?

Is it okay to initialize Mapper in Controller?

Was it helpful?

Solution

The short answer is YES!
If you need to get anything from the database you almost have use the mapper somewhere, because ideally the domain model should not be aware of the database at all or event hat a mapper exists really.
There are I'm sure many strategies and patterns for using and accessing domain models and mappers. I'm also sure that everyone will have differing opinions on how best to utilize these resources. The bottom line is that you have to use what you know how to use, you can always refactor later when you know more.

Just by way of example I'll include a base mapper and a base entity(domain) model.

<?php
/**
 * Base mapper model to build concrete data mappers around.
 * Includes identity map functionallity.
 */
abstract class My_Application_Model_Mapper
{ 
    protected $_tableGateway = NULL;
    protected $_map = array();
    /**
     * Will accept a DbTable model passed or will instantiate
     * a Zend_Db_Table_Abstract object from table name.
     *
     * @param Zend_Db_Table_Abstract $tableGateway
     */
    public function __construct(Zend_Db_Table_Abstract $tableGateway = NULL) {
        if (is_null($tableGateway)) {

            $this->_tableGateway = new Zend_Db_Table($this->_tableName);
        } else {

            $this->_tableGateway = $tableGateway;
        }
    }
    /**
     * @return Zend_Db_Table_Abstract
     */
    protected function _getGateway() {

        return $this->_tableGateway;
    }
    /**
     * @param string $id
     * @param object $entity
     */
    protected function _setMap($id, $entity) {
        $this->_map[$id] = $entity;
    }
    /**
     * @param string $id
     * @return string
     */
    protected function _getMap($id) {
        if (array_key_exists($id, $this->_map)) {
            return $this->_map[$id];
        }
    }
    /**
     * findByColumn() returns an array of rows selected
     * by column name and column value.
     * Optional orderBy value.
     *
     * @param string $column
     * @param string $value
     * @param string $order
     * @return array
     */
    public function findByColumn($column, $value, $order = NULL) {
        $select = $this->_getGateway()->select();
        $select->where("$column = ?", $value);
        if (!is_null($order)) {
            $select->order($order);
        }
        $result = $this->_getGateway()->fetchAll($select);
        $entities = array();
        foreach ($result as $row) {
            $entity = $this->createEntity($row);
            $this->_setMap($row->id, $entity);
            $entities[] = $entity;
        }

        return $entities;
    }
    /**
     * findById() is proxy for find() method and returns
     * an entity object. Utilizes fetchRow() because it returns row object
     * instead of primary key as find() does.
     * @param string $id
     * @return object
     */
    public function findById($id) {
        //return identity map entry if present
        if ($this->_getMap($id)) {
            return $this->_getMap($id);
        }
        $select = $this->_getGateway()->select();
        $select->where('id = ?', $id);
        //result set, fetchRow returns a single row object
        $row = $this->_getGateway()->fetchRow($select);
        //create object
        $entity = $this->createEntity($row);
        //assign object to odentity map
        $this->_setMap($row->id, $entity);

        return $entity;
    }

    /**
     * findAll() is a proxy for the fetchAll() method and returns
     * an array of entity objects.
     * Optional Order parameter. Pass order as string ie. 'id ASC'
     * @param string $order
     * @return array
     */
    public function findAll($order = NULL) {

        $select = $this->_getGateway()->select();

        if (!is_null($order)) {
            $select->order($order);
        }
        $rowset = $this->_getGateway()->fetchAll($select);
        $entities = array();
        foreach ($rowset as $row) {
            $entity = $this->createEntity($row);
            $this->_setMap($row->id, $entity);
            $entities[] = $entity;
        }
        return $entities;
    }
    /**
     * Abstract method to be implemented by concrete mappers.
     */
    abstract protected function createEntity($row);
}

Here is the base domain object:

<?php
/**
 * Base domain object
 * includes lazy loading of foreign key objects.
 */
abstract class My_Application_Model_Entity_Abstract
{
    protected $_references = array();

    /**
     * Accepts an array to instantiate the object, else use
     * __set() when creating objects
     * @param array $options
     */
    public function __construct(array $options = NULL) {

        if (is_array($options)) {
            $this->setOptions($options);
        }
    }
    /**
     * @param array $options
     * @return \My_Application_Model_Entity_Abstract
     */
    public function setOptions(array $options) {
        $methods = get_class_methods($this);
        foreach ($options as $key => $value) {
            $method = 'set' . ucfirst($key);
            if (in_array($method, $methods)) {
                $this->$method($value);
            }
        }
        return $this;
    }
    /**
     * Map the setting of non-existing fields to a mutator when
     * possible, otherwise use the matching field
     */
    public function __set($name, $value) {
        $property = '_' . strtolower($name);
        if (!property_exists($this, $property)) {
            throw new \InvalidArgumentException("Setting the property '$property'
                    is not valid for this entity");
        }
        $mutator = 'set' . ucfirst(strtolower($name));
        if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
            $this->$mutator($value);
        } else {
            $this->$property = $value;
        }
        return $this;
    }
    /**
     * Map the getting of non-existing properties to an accessor when
     * possible, otherwise use the matching field
     */
    public function __get($name) {
        $property = '_' . strtolower($name);
        if (!property_exists($this, $property)) {
            throw new \InvalidArgumentException(
                    "Getting the property '$property' is not valid for this entity");
        }
        $accessor = 'get' . ucfirst(strtolower($name));
        return (method_exists($this, $accessor) && is_callable(array(
                    $this, $accessor))) ? $this->$accessor() : $this->$property;
    }
    /**
     * Get the entity fields.
     */
    public function toArray() {

        //TODO
    }
    /**
     * set and get for _references array, allows the potential to lazy load
     * foreign objects.
     */
    public function setReferenceId($name, $id) {

        $this->_references[$name] = $id;
    }
    public function getReferenceId($name) {
        if (isset($this->_references[$name])) {
            return $this->_references[$name];
        }
    }
}

I have referenced many tutorials and books to finally get my head around these concepts and techniques.
The use of these objects is as needed, if you need to pull an object from the DB you call the mapper, if you need to build an object with form data (or some other data) you can call the object directly.

I hope this helps some. Good Luck!

OTHER TIPS

I use the same architecture and design patterns in all my ZF work. Your mappers should be the only classes in your whole system that access the database. This ensures good Separation of Concerns.

I've toyed a bit with using thin wrapper methods in my Models, such as:

class Application_Model_Foo {

    public function save() {
        $mapper = $this->_getMapper();
        $mapper->save($this);
    }

}

This enables me to call something like:

$foo = new Application_Model_Foo();
$foo->setBar('baz-bum');

$foo->save();

But this makes testing more complicated and muddies the water when it comes to SoC. Lately I've been removing such occurrences from my code in favor of just calling up the mapper directly, as in:

$foo = new Application_Model_Foo();
$foo->setBar('baz-bum');

$mapper = new Application_Model_FooMapper();
$mapper->save($foo);

The latter example means one more line of code in my controller method, but for simplicity in testing I think it's worth it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top