4

I have question about how to "correctly" write my application.

I would like to follow the service/DAO/mapper/entity model and I think that I fully understand it but I found out that some things which I want to do might collide with general conception of this model.

I have these database tables: Product table and it's related table - manufacturers

I have simple ProductDbMapper which gets product's data from database and returns them as a Product entity so I can use it like this:

echo $productEntity->getName();
$productEntity->setPrice(29.9);

I think that it would be great to be able to use somethink like this:

echo $productEntity->getManufacturer()->getName();

The "getManufacturer()" method would simply query another service/DAO for the manufacturer by id. I know that the correct way how to get manufacturer of a product is:

$product = $productService->getProduct(5);
$manufacturer = $manufacturerService->getManufacturerByProduct($product);

But I think that "the fluent" solution is much simplier - it's easy to understand and fun to use. Actually it's pretty natural. I tried to implement this by callbacks. I passed the callback which calls manufacturer's service to Product entity in my ProductMapper.

Problem is that it looks like I'm trying to follow the 5 layer model and at the same time I'm trying to avoid it. So my question is: does this look like a good solution? Does it make sense? How could I achieve the same (magic) in better way?

grongor
  • 1,305
  • 1
  • 13
  • 26
  • 1
    Aside: Storing monetary values as floating-point is never a good idea. Try `DECIMAL` instead. – DCoder Dec 01 '12 at 17:36
  • Thank you, I really didn't know that. I'll change it :-) – grongor Dec 01 '12 at 17:42
  • 1
    It seems like you're looking for an ORM. Look at this question: http://stackoverflow.com/questions/108699/good-php-orm-library – JB Nizet Dec 01 '12 at 17:58
  • I looked at existing ORMs and I don't like them at all. I would like to write all of that by myself. The only problem I have are those "magic methods". – grongor Dec 01 '12 at 18:24
  • I tried Doctrine 2 and after some time I'm quite confident with it.Thank you for pointing me on it. – grongor Dec 07 '12 at 18:10

3 Answers3

9

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.

Community
  • 1
  • 1
mayconbordin
  • 1,494
  • 14
  • 14
  • Thank you, yes, I was thinking about something like this. Could you please fill in the example so I will see whole "actions" from presenter down to the mapper? – grongor Dec 01 '12 at 20:37
  • Thank you. As I look at it it seems to me exactly the same as my "callback solution". In your solution you have to create dedicated class where I have to add special preperties and alter the setters/getters. Your solution seems to be cleaner. But I have different problem now - when I tried to inject manufacturer's service into product's mapper I run into a loop: CatalogService -> ProductDAO -> ProductDbMapper -> (need ManufacturerService: ManufacturerService -> ManufacturerDAO -> ManufacturerDbMapper -> (need CatalogService: CatalogService -> ProductDAO -> ... LOOP HERE ). How to solve that? – grongor Dec 02 '12 at 10:38
  • When you have an application with layers a common good practice is to respect the layer levels. It means that one layer can only talk with his own layer or the layer below him. For example, a DAO can only talk to other DAOs and Data Mappers, but it shouldn't be aware of the Service Layer.In your case, the Data Mapper is the lowest layer, so it should only talk with other data mappers. – mayconbordin Dec 02 '12 at 10:57
  • I get this. Can you then tell me how you pass $this->manufacturerDao in ProductMapper to the new ManufacturerProxy? If you want to initiate ManufacturerProxy in the ProductMapper then you have to pass ManufacturerDao into ProductMapper. – grongor Dec 02 '12 at 11:53
  • You could create an instance of ProductDAO and ManufacturerDAO in the constructor of the ProductMapper or you could use a factory to create those instances. Or you could use Dependency Injection, like in [this question](http://stackoverflow.com/questions/10064970/php-dependency-injection). – mayconbordin Dec 02 '12 at 13:05
3

I've been doing some research for couple of days and I came to conclusion that the best way to achieve what I want is to stop trying to code it myself and just learn to use existing ORM. Because everything what I've been trying to do was creating of my own ORM. And I know that this is bad idea. I will stick with Doctrine 2.

grongor
  • 1,305
  • 1
  • 13
  • 26
1

Do't get me wrong, I do not want to be critic. Why don't join these two tables and then have just one class extending another? Like that you will have $this->manufacturer_name = $row['name']. And for other manufacturer rows as well.

Geo
  • 12,666
  • 4
  • 40
  • 55
pregmatch
  • 2,629
  • 6
  • 31
  • 68
  • I get your point. The thing is that this is not efficient in most cases. I will need to get product's manufacturer in like 1 of 5 times. And it is not just about product's manufacturer - in the future there could be another entities with same problem and with even less "usage times". This is why I would like to avoid this approach. – grongor Dec 01 '12 at 19:35
  • I understand. Then maybe to add one more column into products table, something like show_manufacturer enum ("1","0") default 0 and then in class to check if 0 needed if 1 not needed.... – pregmatch Dec 01 '12 at 19:53
  • Sorry but this really isn't the solution. – grongor Dec 01 '12 at 20:32