Page 1 of 1

How to Properly Enforce Correct Usage of a Class Method?

Posted: Mon Oct 19, 2009 3:02 pm
by gabriel1836
Our current ORM solution uses Data Mappers to represent tables / views in the database which then return a Collection object that can be used to iterate through the retrieved records as Model objects. Between the Data Mapper and Model layers is a Repository layer that handles domain requests to the data mappers and returns the corresponding collections or domain objects.

We are currently looking at refactoring the responsibilities of the Repository and Data Mapper layers so that all application requests to the Data Mapper layer are routed through the Repository and the Data Mapper returns the retrieved data rows to the Repository which then returns the necessary collection to the requesting object.

What I'm wondering is whether it is valid / good practice to pass the entire Repository object into the corresponding Data Mapper so that we can enforce access to the Data Mappers only through the Repository layer.

As an example this is basically how it works now:

Code: Select all

class DataMapper {
 
 public function findAll(Criteria $criteria) 
 {
  $select = $criteria->getSelect();
 
  // Build specific select statement
 
  $rows = $this->_fetchAll($select);
 
  return new Collection(array('data' => $rows, 'mapper' => get_class($this)));
 }
}
I thinking of doing something like this:

Code: Select all

class Repository {
 
 public function findAllByName(Model $model)
 {
  $this->_criteria->addCondition('name LIKE ?', $model->name);
 
  $rows = $this->_mapper->findAll($this);
 
  return new Collection(array('data' => $rows, 'repository' => get_class($this)));
 }
 
} 
 
class DataMapper {
 
 public function findAll(Repository $repository) 
 {
  $select = $repository->getCriteria()->getSelect();
 
  // Build specific select statement
 
  $rows = $this->_fetchAll($select);
 
  return $rows;
 }
}
And then in this version, the Collection object would issue a call to the Repository which could first search through its cached objects and then only issue a call to the database to load the record if its needed.

Re: How to Properly Enforce Correct Usage of a Class Method?

Posted: Mon Oct 19, 2009 3:36 pm
by Christopher
Is the question about enforcing the interface or whether this design is a good idea?

Re: How to Properly Enforce Correct Usage of a Class Method?

Posted: Mon Oct 19, 2009 4:13 pm
by gabriel1836
This post is about both about enforcing the interface and whether this particular design is any good.

I am currently using very distinct layers to handle the abstraction of concerns. The domain model and data mapper layers are very well defined but the repository layer that sits as an intermediary between isn't so much.

I am working to try and reduce some of the technical debt in our library and its got me looking at the relationship between the repository and data mapper classes. Any insights that can be offered would be welcome.

Re: How to Properly Enforce Correct Usage of a Class Method?

Posted: Mon Oct 19, 2009 5:32 pm
by Christopher
It is my understanding that the Repository would have the same interface as the DataMappers -- so it would receive a Criteria. I think the usual reason for using a Repository is to move duplicate code in the DataMappers to a higher layer. I wouldn't have the the DataMappers have a dependency on the Repository. That sounds wrong and backwards.

+1 for use the current hot buzzword "technical debt" ;)

Re: How to Properly Enforce Correct Usage of a Class Method?

Posted: Tue Oct 20, 2009 4:37 pm
by josh
gabriel1836 wrote:Data Mapper returns the retrieved data rows to the Repository which then returns the necessary collection to the requesting object
Data mappers return object models, not data rows! Otherwise they are actually table gateways!

Re: How to Properly Enforce Correct Usage of a Class Method?

Posted: Tue Oct 20, 2009 5:06 pm
by gabriel1836
Data mappers return object models, not data rows! Otherwise they are actually table gateways!
Well that places me in a quandry then doesn't it since Table Gateways only represent a single table and my ORM layer can represent a single table, a view, or multiple tables.

So now that I'm not using either Data Mappers or Table Gateways, what is my ORM layer composed of?

Just to clarify here, the data row is being returned to the Repository layer which then returns an object. The Repository layer sits between the Domain and Database layers to facilitate communication back and forth.

Re: How to Properly Enforce Correct Usage of a Class Method?

Posted: Tue Oct 20, 2009 5:52 pm
by Christopher
Just to be clear -- an ORM layer can retrieve and persist objects that contain data from a single table, a view, or multiple tables.
gabriel1836 wrote:Just to clarify here, the data row is being returned to the Repository layer which then returns an object. The Repository layer sits between the Domain and Database layers to facilitate communication back and forth.
Then your Repository is doing the "Data Mapping".

Re: How to Properly Enforce Correct Usage of a Class Method?

Posted: Wed Oct 21, 2009 4:23 am
by josh
Yes I think your components are mis-labelled. Take a look at my sig link for an ex. of what a basic datamapper functionality does

A repository would be an object you could instantiate and use something like:

Code: Select all

 
$repo = new Repo();
$repo->addCriteria( Critera_Username::eq( 'foo' );
$domainModels = $repo->getFiltered();
 
Internally the repo would use a query object, and submit that to the data mapper

Although, this is overkill IMO. My workflow is to create a mapper for each logical entity, and create specialized finders. Later on I can send a database guy 50 or so mappers and have him rewrite the schema or query strategy

so I can just do $user = $dataMapper->useSpecialFindByUsernameMethod( 'foo' )

Sometimes I build these little simplistic query objects specific to 1 domain only each. Maybe someday I'll build more of a framework for that. This is useful when there would be too many finders,

like finding messages unread but archived
finding read by unarchived
finding read and in inbox
finding read and in sent
finding unreading in inbox
finding unread in sent

etc...

( you don't want to hard code finders in this case. This is where Repository & Query object pattern makes sense)

For example I avoided what I feel is a lot of complex code by using this little helper inside of my mapper:

Code: Select all

 
<?php
class Message_Mapper_Filter
{
    public function notDraftNotTrash()
    {
        return '`draft` = 0 && `trash` = 0';   
    }
    
    public function draftAndNotTrash()
    {
        return '`draft` = 1 && `trash` = 0';   
    }
    
    public function unarchived()
    {
        return $this->notDraftNotTrash() . ' && `archived` = 0';
    }
    
    public function archived()
    {
        return $this->notDraftNotTrash() . ' && `archived` = 1';
    }
    
    public function unread()
    {
        return $this->unarchived() . ' && `read` = 0';
    }
    
    public function inbox( User $user )
    {
         return $this->unarchived() . ' && `recipient_id` = ' . (int)$user->getId();
    }
    
    public function sent( User $user )
    {
         return $this->unarchived() . ' && `sender_id` = ' . (int)$user->getId();
    }
    
    public function all( User $user )
    {
        $id = (int)$user->getId(); 
        return $this->notDraftNotTrash() . ' && ( `sender_id` = ' . $id . ' || `recipient_id` = ' . $id . ')';
    }
    
    public function sender( User $user )
    {
        return '`sender_id` = ' . $user->getId();
    }
    
    public function trash( User $user )
    {
        $id = (int)$user->getId();
        return '`trash` = 1 && ( `sender_id` = ' . $id . ' || `recipient_id` = ' . $id . ')';
    }
}
Then I use these basic building blocks to create a lot of finders on the fly that end up looking like:

Code: Select all

 
/**
    * List archived messages for a given user
    * 
    * @param User the user to list the archived messages for
    * @return Shuffler_Collection collection of Message objects
    */
    public function listArchived( User $user, $offset = NULL, $limit = NULL )
    {
        return $this->listWhere( $this->filter->archived() . ' && ' . $this->filter->sender( $user ), $offset, $limit );
    }
I think the most important thing you can do is unit test your mappers, honestly. For this you need to be using innoDb to do it right! You should test a full save & a full read!