New PHP Data Mapper Library

Now this is interesting.
The stumbling block I have with TomB’s method - and with the whole mapper thing - is handing off dependencies to relations.
How do you provide the required Mapper to the Entity? In Tom’s example, the mapper is generated from scratch internally - which is (as I understand it) a Unit Testing no no. Next step - as TomB indicates - is to use a factory. So now, you’re handing a factory or DI container to the Entity, at least all your responsibilities for object generation are handled in one place.

But as you iterate down through the chain, handing database adapters and factories down the chain, you’re setting up all these references right? Worse still, I like to try and cache resources that have been instantiated in my factory classes, so if the generated object contains a reference to the factory itself, bingo - there’s a circular reference.

The latest idea I’m thinking of trying is a proxy wrapper as suggested by Anthony Sterling which contains the factory and injects the required mapper in much the same way as TomB invokes the getMapper method - just handled externally by the wrapper i.e.

class Proxy{

private $entity;
private $factory;

public function __construct($entity,$factory){
    $this->entity = $entity;
    $this->factory = $factory;
    $this->relations = $this->entity->getRelations;
    $this->properties = $this->entity->getProperties;
    }

public function __get($key){
    if(array_key_exists($this->properties)){
       return $this->entity->$key;
    }

    if(array_key_exists($this->relations)){
        $mapper = $this->factory->getMapper($key);
        $child_entity = $mapper->load($Key,array($this->relations['foreignField']=>$this->relations['localField']));//at this point, the child entity is cached
        return $this->factory->getProxy($child_entity,$this->factory);
    }

}

So, should I ditch the caching to prevent circular references? Should I ditch the proxy and just generate objects with “new” (if there is a specific method for generating the mapper, then I guess it’s testable right?).
Which brings us round to the last issue - if the entity classes have the capability for generating mappers, then they have the capacity to save themselves - albeit via the mapper (the syntactic sugar) - is this a corruption of data mapper. If the mapper is generated by a factory then we can alter the mapper they recieve externally through configuration right?

Man, my head is spinning - anyone want to get me out of this tangle?

Yup, avoid circular references at all costs. That is one of the main reasons I use the central Pheasant object, such that it maintains associations between domain objects and mappers and finders.

PHP 5.3 does a much better job at detecting circular references when it’s freeing memory, but I still don’t trust it :slight_smile:

Not sure I entirely agree, I see the core of the data mapper pattern as separating the persistence into separate mapper objects which can be interchanged to support different data layers. I agree that it’s somewhere in the middle, not sure it particularly matters. As soon as there is trait support I’ll hopefully be able to move to that.

It seemed like a reasonable middle ground to me. Any particular problems you see with the base class approach?

Having the domain objects extend from a common base class pretty much disqualifies it as an object mapper.

Hi…

Sorry, not a criticism of you. I was thinking of the blog post and that it was filed as a bug. Very strange.

yours, Marcus

Basically the save() methods are syntactic sugar that delegate to the registered mapper.

Whether that makes it activerecord, I’m not sure.

I posted a full code solution :stuck_out_tongue:

Yes it uses __get.

Essentially:

-Each object knows of the mapper it was created by.
-Each mapper knows how it is related to other mappers
-When you call __get it queries the related (via its own) mapper to find the related record(s).
-As each object is created, it is passed the mapper it was created by. This allows for unlimited chaining :slight_smile:

It also means any object, at any level of the chain can be initiated through its mapper and used as the root object.

Could you elaborate on that please?

[QUOTE=TomB;]$user = $userMapper->findById(1);
echo $user->orders[0]->items[0]->product->manufacturer->name [/QUOTE]

Forgive me for being dense Tom - but how would you achieve this?
Do you load the entire object graph on the initial query?
Are you using some sort of magic method (i.e. __get) to instantiate mappers when data that points to relations is requested?

Chaining (as I understand it) would involve returning an object of some sort - be it the original root object, or I guess you’re drilling down to each object in some way?

Hi…

It looks more like ActiveRecord to me. You have each persistent object extending DomainObject and you have to call save() on each one. Or have I missed something?

yours, Marcus