phpro / zf-doctrine-hydration-module

Configurable Doctrine hydrators for ZF2
18 stars 33 forks source link

Allow strategy configuration #12

Closed gsomoza closed 9 years ago

gsomoza commented 9 years ago

I have a use-case where I have several entities with different one-to-one relationships, and I want to be able to use a single strategy to extract a given field.

Currently it would seem this is only possibly by creating a strategy for each field name. E.g. "ExtractDateField".

But I'd like to use a more generic approach where I'd be able to create a generic strategy and configure it on a per-entity basis. E.g. create an "ExtractFieldStrategy", that would accept a "field_name" setting / configuration.

This PR attempts to allow that by providing an interface that can be incorporated by strategies that desire this functionality: StrategyConfigAwareInterface.

There are other ways of doing this (e.g. firing an event might be a strong candidate to consider). So let me know if you're interested in the current approach and I'll write tests, docs, etc. Otherwise let me know what other approach you would recommend.

One last note: I wanted to avoid breaking BC, that's why the strategy config is under another key.

veewee commented 9 years ago

@gabrielsomoza The ZF2 hydration strategy is meant to only extract or hydrate one specific field. Can you please give me an example of how I could use this functionality? It's not clear to me what it should do exactly.

Maybe you mean this:

<?php
class User {
    /**
     * @var Address
     */
    protected $billingAddress;
}

class Address {
    /**
     * @var string
     */
    protected $street;
}

I would create 2 hydrators:

and 1 strategy:

The AddressHydratorStrategy will use the AddressHydrator internally. This makes it easy to reuse existing hydrators / strategies without to much configuration. If you want to hydrate the Address you can use the hydrator stand alone. If you want to hydrate the addess in another entity, you could just use the strategy.

Thanks for your PR and I am looking forward to your reply!

gsomoza commented 9 years ago

Here's my situation:

class User {
  /** @ORM\ManyToOne(targetEntity=Department) **/ 
  private $department;

  /** @ORM\ManyToOne(targetEntity=Club) **/ 
  private $club;
}

class Department {
  /** @var string **/
  private $name; // with setters/getters of course
}

class Club {
  /** @var string **/
  private $code;
}

And I would like to be able to extract for a given User its department's name and the club's code using the same strategy. The reason to use the same strategy is that there are dozens of classes with different field names that need to be extracted the same way.

class EntityField implements StrategyInterface {
    protected $fieldName; // with getters and setters

    public function extract( $value ) {
        if (!is_object($value)) {
            return $value;
        }

        $getter = 'get'.ucfirst($this->getFieldName());

        // Validate object:
        $rc = new \ReflectionClass($value);
        if (!$rc->hasMethod($getter)) {
            return $value;
        }

        return $value->$getter();
    }

    // hydrate does nothing in this case
}

In Apigility this would result in:

{ // GET /api/users/1
  "department": "Public Relations", // department.name
  "club": "CABJ" // club.code
}

Instead of:

{ // GET /api/users/1
  "_embedded": {
    "department": {
      "name": "Public Relations" // this really is the only department field we want to expose
    },
    "club": {
      "code": "CABJ" // same as above for this one here
    }
  }
}

Using a custom hydrator would do the trick but I'm trying to determine if my use-case would be similar to others people's before I make my code-base start to diverge significantly from what's already available out of the box.

Hope this explains it better. To wrap it up I'd like to add that if there's a better way to accomplish this without creating dozens of strategies (for each entity / field combination) I'd appreciate a hint in the right direction.

gsomoza commented 9 years ago

By the way:

The ZF2 hydration strategy is meant to only extract or hydrate one specific field.

This IS the case in my PR: the strategy would apply to a single field - which HAS to be an association or return an object - and would allow to extract a single sub-field from the associated entity / resulting object. Instead of extracting the entity / object as a whole.

veewee commented 9 years ago

It looks like we need some kind of StategyFactory. At the moment there is no PluginManager for hydrator strategies in ZF2. I think it's best not to invent new ZF hydrator features in this repository. This repository already covers all basic configurable parts of the ZF hydrator. Maybe adding new functionality to this hydrator is one bridge too far. You could try to get it in the official ZF2 repository, but I think they will prefer explicit hydrators above magic hydrators.

Maybe some other possible solutions for your problem:

boukeversteegh commented 9 years ago

I agree with both of you. Strategies need some easy way to accept configuration, because now they are only useable for one specific field or just reusable by variable type. But I agree that it should be added to core, not here.

gsomoza commented 9 years ago

Makes sense, this discussion could be continued in core.

Ended up using __toString() because in my use-case it does make sense to have the entity know how to represent itself as a string.