Zend Framework DAL: DAOs and DataMappers

A Data Access Layer (DAL) is the layer of your application that provides simplified access to data stored in persistent storage of some kind. For example, the DAL might return a reference to an object complete with its attributes instead of a row of fields from a database table.

A Data Access Objects (DAO) is used to abstract and encapsulate all access to the data source. The DAO manages the connection with the data source to obtain and store data. Also, it implements the access mechanism required to work with the data source. The data source could be a persistent store like a database, a file or a Web service.

And finally, the DataMapper pattern is used to move data between the object and a database while keeping them independent of each other. The DataMapper main responsibility is to transfer data between the two and also to isolate them from each other.

Here’s an example of the DataMapper pattern:

Database Table Structure

CREATE TABLE `user` (
 `id` int(11) NOT NULL auto_increment,
 `first_name` varchar(100) NOT NULL,
 `last_name` varchar(100) NOT NULL,
 PRIMARY KEY  (`id`)
)

The User DAO

The DAO pattern provides a simple, consistent API for data access that does not require knowledge of an ORM interface. DAO does not just apply to simple mappings of one object to one relational table, but also allows complex queries to be performed and allows for stored procedures and database views to be mapped into data structures.

A typical DAO design pattern interface is shown below:

interface UserDao
{
    public function fetchRow($id);
    public function fetchAll();
    public function insert($data);
    public function update($id, $data);
    public function delete($id);
}

class UserDatabaseDao implements UserDao
{
    public function fetchRow($id)
    {
        $dataSource = Zf_Orm_Manager::getInstance()->getDataSource();
        $db = $dataSource->getConnection('slave');
        $query = $db->select()->from('user')->where('id = ?', $id);
        return $db->fetchRow($query);
    }

    public function fetchAll()
    {
        $dataSource = Zf_Orm_Manager::getInstance()->getDataSource();
        $db = $dataSource->getConnection('slave');
        $query = $db->select()->from('user');
        return $db->fetchAll($query);
    }

    public function insert($data)
    {
        $dataSource = Zf_Orm_Manager::getInstance()->getDataSource();
        $db = $dataSource->getConnection('master');
        $db->insert('user', $data);
        return $db->lastInsertId();
    }

    public function update($id, $data)
    {
        $dataSource = Zf_Orm_Manager::getInstance()->getDataSource();
        $db = $dataSource->getConnection('master');
        $condition = $db->quoteInto('id = ?', $id);
        return $db->update('user', $data, $condition);
    }

    public function delete($id)
    {
        $dataSource = Zf_Orm_Manager::getInstance()->getDataSource();
        $db = $dataSource->getConnection('master');
        $condition = $db->quoteInto('id = ?', $id);
        return $db->delete('user', $condition);
    }
}

The User Entity

An Entity is anything that has continuity through a life cycle and distinctions independent of attributes that are important to the application’s user.

class User
{
    protected $id;
    protected $firstName;
    protected $lastName;

    public function setId() {
    }
    public function getId() {
    }
    public function setFirstName() {
    }
    public function getFirstName() {
    }
    public function setLastName() {
    }
    public function getLastName() {
    }
    public function toArray() {
    }
}

The User DataMapper

class UserDataMapper extends Zf_Orm_DataMapper
{
    public function __construct()
    {
        $this->setMap(
            array(
                'id'         => 'id',
                'first_name' => 'firstName',
                'last_name'  => 'lastName'
            )
        );
    }
}

Source Code: http://fedecarg.com/repositories/show/datamapper

The User Repository

Repositories play an important part in DDD, they speak the language of the domain and act as mediators between the domain and data mapping layers. They provide a common language to all team members by translating technical terminology into business terminology.

Lets create a UserRepository class to isolate the domain object from details of the DAO:

class UserRepository
{
    private $databaseDao;

    public funciton setDatabaseDao(UserDao $dao)
    {
        $this->databaseDao = $dao;
    }

    public function getDatabaseDao()
    {
        if (null === $this->databaseDao) {
            $this->setDatabaseDao(new UserDatabaseDao());
        }
        return $this->databaseDao;
    }

    public function find($id)
    {
        $row = $this->getDatabaseDao()->fetchRow($id);
        $mapper = new UserDataMapper();
        $user = $mapper->assign(new User(), $row);

        return $user;
    }
}

The User Controller

class UserController extends Zend_Controller_Action
{
    public function viewAction()
    {
        $userRepository = new UserRepository();
        $user = $userRepository->find($this->_getParam('id'));
        if ($user instanceof User) {
            $id = $user->getId();
            $firstName = $user->getFirstName();
            $lastName = $user->getLastName();
            // get an array of key value pairs
            $row = $user->toArray();
        }
    }
}

That’s all, I hope you’ve found this post useful.

About these ads

16 thoughts on “Zend Framework DAL: DAOs and DataMappers

  1. Hi Federico,
    Zend_Entity and Doctrine 2 are in-development and will be generic DataMapper implementation driven by annotation or yaml/xml metadata, like Hibernate for Java. I started to contribute to both and their objective is also to eliminate the requirement for custom classes, speeding up development.

  2. Hi Giorgio,

    Yes, I’m following the development of ZF and Doctrine very closely. I like Benjamin’s proposal (http://bit.ly/JrSM6), and it’s great that Zend is supporting it and that you are helping him. I’m glad Zend finally decided to adopt these design patterns that have proven to increase the efficiency of the coding process.

  3. Following Matthew Weier O’Phinney’s blog and tweets I guess they are willing to improve the design, but since it is a big and established project they are slowed down by backward compatibility.

  4. Hey Federico,

    I think there is something that doesn’t quite feel right with your code. The idea of working with DAL’s is a great idea, but I think that your implementation is still too tightly coupled to the Db:

    In your Project_Model_User, you get an instance from the Db. But the problem is that when you want to change your persistance from Db to let’s say Soap, you have to change all your models, because they’re now coupled to the Db.

    Models should not have to know where their data comes from. Well actually, they should: from your Datamapper. So it seems more logical, to let the Datamapper care about which DAO should be used.

    Maybe it’s just a matter of opinion or taste, but what do you think?

  5. Hi Tom,

    Yes, you are right. The DataMapper should expose some methods that permit the Domain Model to do whatever it needs to do, while keeping it isolated from the database. I’ve updated the example, thanks.

    Umpirsky,

    Done, I got rid of the exceptions to avoid confusion. Thanks.

  6. Pingback: Zend Framework University

  7. Hi guys what about making DAO/DAL generator not via yaml or xml but via db mapper, like Henrik Hussfelt does: http://hussfelt.net/labs/zend-model-creator ?

    Only issues I spot there are composite primary key mapping problems (always gets the last one instead native primary key) and some Flex/AMF incompatibilities (returned arrays are empty and need recast).

  8. Hi Federico,

    Great post with nice examples. I’m going to follow this in one of my projects soon and a few question struck me. Thought I’d share here.

    I am curious about a scenario where a record retrieval depends on sub records. For example, let’s say we store multiple addresses for each user as follows:


    CREATE TABLE `address` (
    `id` int(11) NOT NULL auto_increment,
    `user_id` int(11) NOT NULL,
    `street` varchar(100) NULL,
    `city` varchar(100) NULL,
    `state` varchar(100) NULL,
    PRIMARY KEY (`id`)
    )

    Now, when I use $user->getAddresses() function from controller, I expect to receive the addresses associated with this user entity. How should this be handled – in the Entity and Mapper ?

    Another question of mine, when we are doing dependency injection as the following code:


    $mapper = new Project_DataMapper_User();
    $mapper->setEntity(new Project_Entity_User());
    $user = $mapper->get($id);

    How should I return multiple instance of the entity, for example in a $mapper->getBySomeCriteria function?

    I appreciate any help and thanks in advance.

  9. Hi Federico,

    Thanks for your response and the code examples – things are clearer now.

    I was checking the ZF quick-start tutorial on this URL: http://framework.zend.com/docs/quickstart/create-a-model-and-database-table. It shows an integration between the Domain Entity and the Domain Model.

    Although to my eyes it seems to be pretty much the same, but do you see any foreseeable problem with that approach? Your comment would be appreciated.

    Thanks

    Emran

  10. Yes, a month after I published this post Matthew gave a talk about “Architecting Your Models” http://bit.ly/5dHrzv and the manual was updated.

    The idea behind this post was to show how the Data Mapper pattern works independently of the Table Data Gateway pattern.

  11. Hi All,

    While implementing this DataMapper pattern in my project, I was thinking that we’re using the DataMappers to abstract away the DAOs from the models – so that we can swap a DAO whenever we need (say for caching). However, we have to keep the interface of the DAOs same so that the mapper can work with it without knowing anything about the implementation details – it just needs to know the functions that are same across all the DAO implementations.

    Now, how about using a Factory to do the same? As the DAOs are implementing the same interface, models need not to know more details about their inner implementation. Consider the following code (written in Project_Model_User:


    $dao = DaoFactory::create("Db");
    $dao->setTable("users");
    $user = $dao->find(13);

    In a similar way, we can do the following, based on logic/settings:

    $dao = DaoFactory::create("ZohoCloudDB");
    $dao = DaoFactory::create("MyService");

    What are the pros/cons of this approach? Any thoughts from anybody?

    Thanks

  12. What I’m doing is passing a Loader (Factory) and Injector to the ORM Manager, who’s responsible for loading the DataMapper and injecting dependencies. I’m not using a Locator class, but you can use one if you want, I guess it depends on the directory structure of your project.

  13. I was looking for good code examples using both DAO and repositories and fell over your site. I don’t use Zend (or PHP for that matter) so excuse me if I have misunderstood something.

    You are giving a simple and great overview of the different responsibilities, I did however not like the line:
    $this->setDatabaseDao(new UserDatabaseDao());

    Here you’re coupling the repository layer directly with the DAO implementation. This is perhaps also what Emran is saying, but on C# I would use some kind of “inversion of control container” for this. Injecting the given implementation into the class through your repository constructor.

    If this is not possible in Zend, I would the very least put the instantation of the DAO classes in some factory to keep all the coupling to specific DAO classes in one place.

    Thanks for sharing…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s