22

Extended Question: Why should I use data mapper / Db_Table_Row, where as DbTable is capable of handling most of the basic tasks for data manipulation.

I am currently learning ZF v1.11

For Database manipulation, I created DbTable for each tables. For example, "users" table is represented by Application_Model_DbTable_Users with no additional codes in there.

When manipulating data, I can use:

<?php
$uTable = new Application_Model_DbTable_Users();
$newUid = $uTable->insert(array('name'=>'Old Name', 'email'=>''));
$user = $uTable->find($newUid)->current();

// Then I can use $user which is instance of Table_Row
$user->name = "New Name";
$user->email = "email@addr.com";
$user->save();

My Question is, when would I need to define a row class (assuming Table_Row is referred as DataMapper in ZF-Tutorials)

// By, adding this to the DbTable class
protected $_rowClass = 'Application_Model_User';

What are the benefits of having a Row class for each entity? Can anyone point me to best practices for this.

Hossain Khan
  • 6,332
  • 7
  • 41
  • 55
  • http://stackoverflow.com/questions/373054/ - gives a idea that, sending email or changing password logic can be implemented in Table Row class, which are specific to user. – Hossain Khan Jan 30 '11 at 15:39

2 Answers2

41

You do not need to define your own Table_Row. However, it maybe useful in many cases, particularly if you want to define some specific methods or properties for a given user row. They can also improve readability of your code.

For example in your Users table case, you could define a method called getFullName() in a custom user row as:

public function getFullName() {
    return $this->firstName . ' ' . $this->lastName;
}

Then when you obtain user row object, to get the full name of the user, you just do:

$user = $uTable->find($newUid)->current();
$fullName = $user->getFullName();

Second example is when you have some parent table to the Users table, such as Addresses. In this case you could define a method called getAddress in a user row:

public function getAddress() {
    return $this->findParentRow('Application_Model_DbTable_Addresses');
}

In this scenario, you would get an Address row object for a current user as follows:

$user = $uTable->find($newUid)->current();
$addressRow = $user->getAddress();

Another example, would be when you want to create custom delete or instert methods. Lets assume that you want to make sure you do not want to delete an admin user using delete() method. Then you could overload delete method from Zend_Db_Table_Row as follows:

public function delete() {
        if ('admin' === $this->userRole) {
              return 0;
        }
        return parent::delete();
} 

This way, you would not be able to delete an admin user just by calling delete() on a user row object:

 $user = $uTable->find($newUid)->current();
 $rowsDeleted = $user->delete(); // would be 0 if $user is admin

These are just three basic examples showing usefulness of defining your own row classes. But of course they are not necessary. However, from my own experience they are quite handy.

Marcin
  • 215,873
  • 14
  • 235
  • 294
  • In the answer of question #373054, there is a function sendMailTo for User, is it a good practice to keep non database related functions in Db_Row class? Where should these kind of business logic specific to User stay? – Hossain Khan Feb 01 '11 at 14:53
  • @Hossain Khan. In fact what Bill Karwin presents is your own class Person that contains business logic related to a person. Notice that the Person class does not extend any class. The constructor of this class probably would take an instance of My_Db_Table_Row_Person class or an id of a person. You can think of it as a new abstraction layer specific only to your project where you put your business logic, such as sending emails. As a side note, Bill Karwin actually worked on the development of Zend_Db :). – Marcin Feb 01 '11 at 15:34
20

In a nutshell: it's about isolation.

Zend_Db_Table is an implementation of the Table Data Gateway. It channels CRUD access to a specific table view through one class. It is usually used with a Table Module, e.g. a class that contains the business logic for the records handled by a gateway.

Zend_Db_Table_Row is an implementation of the Row Data Gateway Pattern. Here, the returned objects look exactly like a database record and they contain the business logic to work with that data, but they don't contain the logic to CRUD with the table they are from (that would be an ActiveRecord) but aggregate them.

Row Data Gateways are fine as long as you don't have too much object relational impedance mismatch. How an object is persisted in a relational database and how it should look like in an object world are often quite different things. When using a Domain Model, your business objects are usually structured in a different way than they are stored in the database. Thus, you cannot easily CRUD them from the database. This is where DataMapper comes into play.

A DataMapper takes the responsibility of mapping Domain objects onto Recordsets and vice versa. This makes your application more maintainable because it decouples your Domain objects from the Database structure. It keeps them separated and gives you more flexibility in how to model both layers (persistence and domain).

Gordon
  • 312,688
  • 75
  • 539
  • 559