views:

743

answers:

5

Updated: 09/02/2009 - Revised question, provided better examples, added bounty.


Hi,
I'm building a PHP application using the data mapper pattern between the database and the entities (domain objects). My question is:

What is the best way to encapsulate a commonly performed task?

For example, one common task is retrieving one or more site entities from the site mapper, and their associated (home) page entities from the page mapper. At present, I would do that like this:

$siteMapper = new Site_Mapper();
$site = $siteMapper->findByid(1);

$pageMapper = new Page_Mapper();
$site->addPage($pageMapper->findHome($site->getId()));

Now that's a fairly trivial example, but it gets more complicated in reality, as each site also has an associated locale, and the page actually has multiple revisions (although for the purposes of this task I'd only be interested in the most recent one).

I'm going to need to do this (get the site and associated home page, locale etc.) in multiple places within my application, and I cant think of the best way/place to encapsulate this task, so that I don't have to repeat it all over the place. Ideally I'd like to end up with something like this:

$someObject = new SomeClass();
$site = $someObject->someMethod(1); // or
$sites = $someObject->someOtherMethod();

Where the resulting site entities already have their associated entities created and ready for use.

The same problem occurs when saving these objects back. Say I have a site entity and associated home page entity, and they've both been modified, I have to do something like this:

$siteMapper->save($site);
$pageMapper->save($site->getHomePage());

Again, trivial, but this example is simplified. Duplication of code still applies.

In my mind it makes sense to have some sort of central object that could take care of:

  • Retrieving a site (or sites) and all nessessary associated entities
  • Creating new site entities with new associated entities
  • Taking a site (or sites) and saving it and all associated entities (if they've changed)

So back to my question, what should this object be?

  • The existing mapper object?
  • Something based on the repository pattern?*
  • Something based on the unit of work patten?*
  • Something else?

* I don't fully understand either of these, as you can probably guess.

Is there a standard way to approach this problem, and could someone provide a short description of how they'd implement it? I'm not looking for anyone to provide a fully working implementation, just the theory.

Thanks,
Jack

A: 

I'd probably start by extracting the common task to a helper method somewhere, then waiting to see what the design calls for. It feels like it's too early to tell.

What would you name this method ? The name usually hints at where the method belongs.

Morendil
Hmmm, very good question :-) , I think I need to give this some more thought.
Jack Sleight
+1  A: 

[Edit: this entry attempts to address the fact that it is oftentimes easier to write custom code to directly deal with a situation than it is to try to fit the problem into a pattern.]

Patterns are nice in concept, but they don't always "map". After years of high end PHP development, we have settled on a very direct way of handling such matters. Consider this:

File: Site.php

class Site
{
   public static function Select($ID)
   {
      //Ensure current user has access to ID
      //Lookup and return data
   }

   public static function Insert($aData)
   {
      //Validate $aData
      //In the event of errors, raise a ValidationError($ErrorList)

      //Do whatever it is you are doing

      //Return new ID
   }

   public static function Update($ID, $aData)
   {
      //Validate $aData
      //In the event of errors, raise a ValidationError($ErrorList)

      //Update necessary fields
   }

Then, in order to call it (from anywhere), just run:

$aData = Site::Select(123);

Site::Update(123, array('FirstName' => 'New First Name'));

$ID = Site::Insert(array(...))

One thing to keep in mind about OO programming and PHP... PHP does not keep "state" between requests, so creating an object instance just to have it immediately destroyed does not often make sense.

gahooa
This doesn't really address the problem of working with (creating new, creating existing, and saving) multiple related entities.
Jack Sleight
A: 
class Page {

  public $id, $title, $url;

  public function __construct($id=false) {
    $this->id = $id;
  }

  public function save() {
    // ...
  }

}

class Site {

  public $id = '';
  public $pages = array();

  function __construct($id) {
     $this->id = $id;
     foreach ($this->getPages() as $page_id) {
       $this->pages[] = new Page($page_id);
     }
  }

  private function getPages() {
    // ...
  }

  public function addPage($url) {
    $page = ($this->pages[] = new Page());
    $page->url = $url;
    return $page;
  }

  public function save() {
    foreach ($this->pages as $page) {
      $page->save();
    } 
    // ..
  }

}

$site = new Site($id);
$page = $site->addPage('/');
$page->title = 'Home';
$site->save();
Pies
Although that's an elegant solution, that wouldn't work for my set-up, as per the data mapper pattern, my entities have no knowledge of the mapper, so they have no save method. Entities are parsed back to the mapper's save method to be saved to the DB.
Jack Sleight
+2  A: 

Using the repository/service pattern, your Repository classes would provide a simple CRUD interface for each of your entities, then the Service classes would be an additional layer that performs additional logic like attaching entity dependencies. The rest of your app then only utilizes the Services. Your example might look like this:

$site = $siteService->getSiteById(1); // or
$sites = $siteService->getAllSites();

Then inside the SiteService class you would have something like this:

function getSiteById($id) {
  $site = $siteRepository->getSiteById($id);
  foreach ($pageRepository->getPagesBySiteId($site->id) as $page)
  {
    $site->pages[] = $page;
  }
  return $site;
}

I don't know PHP that well so please excuse if there is something wrong syntactically.

CodeMonkey1
OK, that makes sense, need to read up on the service pattern. However, (and I've run into this question already), what's the distinction between the repository and the mapper. My mappers already provide a CRUD interface, so why the additional repository layer on top?
Jack Sleight
Also, should you then have a service for every type of entity, or just the ones where you wanted to do something bespoke?
Jack Sleight
I think the repository very similar to your mappers and you could probably just keep that. The new component is the service layer.
CodeMonkey1
Ideally your presentation layer would only access the service layer and not the mappers directly (thus needing a service for each entity type, or one for each set of related entity types). That way if some special logic becomes necessary you would only need to modify the service method.
CodeMonkey1
Great, thanks for your help! I've got to get on with actually implementing something soon, so going to sleep on it and decide exactly how to do all this. Probably going to have a couple more questions as well.
Jack Sleight
I've actually decided to use an existing ORM framework for my application now, which solves the problem anyway. I'm accepting this answer as it has the most up votes.
Jack Sleight
A: 

Make your Site object an Aggregate Root to encapsulate the complex association and ensure consistency.

Then create a SiteRepository that has the responsibility of retrieving the Site aggregate and populating its children (including all Pages).

You will not need a separate PageRepository (assuming that you don't make Page a separate Aggregate Root), and your SiteRepository should have the responsibility of retrieving the Page objects as well (in your case by using your existing Mappers).

So:

$siteRepository = new SiteRepository($myDbConfig);
$site = $siteRepository->findById(1); // will have Page children attached

And then the findById method would be responsible for also finding all Page children of the Site. This will have a similar structure to the answer CodeMonkey1 gave, however I believe you will benefit more by using the Aggregate and Repository patterns, rather than creating a specific Service for this task. Any other retrieval/querying/updating of the Site aggregate, including any of its child objects, would be done through the same SiteRepository.

Edit: Here's a short DDD Guide to help you with the terminology, although I'd really recommend reading Evans if you want the whole picture.

Michael Hart
OK, so you'd set up a "repository" to do a similar task to the service layer that CodeMonkey1 suggested? The mixing up of terms really confuses me (and is one of the reasons had to I ask this question) it seems (and not just on SO) people use mapper, repository and service layer interchangeably.
Jack Sleight
You're right, the terms can be quite confusing - although this is tagged as Domain Driven Design, which is why I was using DDD terms. In DDD, a Repository has the task of hiding the persistence layer to the rest of the domain. I don't think a Service is necessary in this case.
Michael Hart