If you want to stick with the Data Mapper pattern, you can either load the product from the database with all its dependencies (manufacturer, stock, tax) or you can use lazy loading. The first option is not a good practice. But to use lazy loading you will need an additional layer obtained through a virtual proxy.
Why? Because otherwise you would have to put some database code inside your entity, and the whole idea about those layers is decoupling.
So, what's a virtual proxy?
According to Martin Fowler (2003):
A virtual proxy is an object that looks like the object that should be in the field, but doesn't actually contain anything. Only when one of its methods is called does it load the correct object from the database.
Example
The interface defines the methods that will be implemented by the real entities and virtual proxies:
// The interface
interface ManufacturerInterface
{
public function getName();
}
This is the entity, you are probably extending some generic model class too:
// The concrete Manufacturer class
class Manufacturer implements ManufacturerInterface
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
And this is the proxy:
// The proxy
class ManufacturerProxy implements ManufacturerInterface
{
private $dao;
private $manufacturer;
private $manufacturerId;
public function __construct($dao, $manufacturerId)
{
$this->dao = $dao;
// set manufacturer to NULL to indicate we haven't loaded yet
$this->manufacturer = null;
}
public function getName()
{
if ($this->manufacturer === null)
{
$this->lazyLoad();
}
return $this->manufacturer->getName();
}
private function lazyLoad()
{
$this->manufacturer = $this->dao->getById($this->manufacturerId);
}
}
The proxy will be used by the Data Mapper when instantiating other classes with some relationship with the Manufacturer, like the Product. So, when a product is instantiated, it's fields are filled and the manufacturer field receives an instance of ManufacturerProxy instead of Manufacturer.
The implementation of the Data Mapper will differ, so I'll give a simple example:
class ProductMapper {
private $dao;
private $manufacturerDao;
// .......
public function find($id) {
// call the DAO to fetch the product from database (or other source)
// returns an associative array
$product = $this->dao->find($id);
// instantiate the class with the associative array
$obj = new Product($product);
// create the virtual proxy
$obj->manufacturer = new ManufacturerProxy($this->manufacturerDao, $product['manufacturer_id']);
return $obj;
}
}
As I said, the example above is very simple. I've included the DAO as you're using it, but authors like Martin Fowler give the task of handling SQL queries or any other underlying technology to the Data Mapper. But there's also authors like Nock (2004) that employ both the Data Mapper and the Data Accessor.
With a more complex Data Mapper, you could only have files in a language like XML or YAML, similar Doctrine or Hibernate.
Just to finish, the service:
class ProductService {
private $productMapper;
/**
* Execute some service
* @param {int} $productId The id of the product
*/
public function someService($producId) {
// get the product from the database
// in this case the mapper is handling the DAO
// maybe the data mapper shouldn't be aware of the DAO
$product = $this->productMapper->find($productId);
// now the manufacturer has been loaded from the database
$manufacturerName = $product->manufacturer->getName();
}
}
Maybe you could make the DAO call the Data Mapper to create the entities and then use the DAO in the Service. It is actually simpler, because you don't need to repeat the methods twice, but that's up to you.
The Alternative
If you don't want to implement the virtual proxy, and still want a fluent interface, you could switch to the Active Record pattern. There are some libraries that can do the job, like PHP-ActiveRecord and Paris. Or if you want to do it by yourself, you can look here and here. The book by Martin Fowler is also a good start.
References
FOWLER, Martin. Patterns of Enterprise Application Architecture. Addison-Wesley, 2003.
NOCK, CLIFTON. Data Access Patterns: Database Interactions in Object-Oriented Applications. Addison-Wesley, 2004.