-
-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generated hydrator and extractor should be a closure, not a class #64
Comments
I have though of this, here are my ideas. Nominal use case, no inheritanceclass A {
private $a;
} Generated closure: $hydrateA = Closure::bind(function (array $data, A $object) {
$object->a = $values['a'];
}, null, A::class); Hydration: $object = new A();
$data = ['a' => 1];
$hydrateA($data, $object); Notes:
Inheritance with visible properties use caseclass B {
protected $b;
}
class A extends B {
private $a;
} Generated closure: $hydrateA = Closure::bind(function (array $data, A $object) {
$object->b = $values['b'];
$object->a = $values['a'];
}, null, A::class); Hydration: $object = new A();
$data = ['a' => 1, 'b' => 2];
$hydrateA($data, $object); Notes:
Inheritance with hidden properties use caseclass B {
private $b;
}
class A extends B {
private $a;
} Generated closure: $hydrateB = Closure::bind(function (array $data, B $object) {
$object->b = $values['b'];
}, null, B::class);
$hydrateA = Closure::bind(function (array $data, A $object) use ($hydrateB) {
$hydrateB->__invoke($data, $object);
$object->a = $values['a'];
}, null, A::class); Hydration: $object = new A();
$data = ['a' => 1, 'b' => 2];
$hydrateA($data, $object); Notes:
Deep inheritance with hidden properties use caseclass C {
private $c;
}
class B extends C {
private $b;
}
class A extends B {
private $a;
} Generated closure: $hydrateC = Closure::bind(function (array $data, C $object) {
$object->c = $values['c'];
}, null, C::class);
$hydrateB = Closure::bind(function (array $data, B $object) use ($hydrateC) {
$hydrateC->__invoke($data, $object);
$object->b = $values['b'];
}, null, B::class);
$hydrateA = Closure::bind(function (array $data, A $object) use ($hydrateB) {
$hydrateB->__invoke($data, $object);
$object->a = $values['a'];
}, null, A::class); Hydration: $object = new A();
$data = ['a' => 1, 'b' => 2, 'c' => 3];
$hydrateA($data, $object); Notes:
Deep inheritance with mixed properties use caseclass C {
private $c;
}
class B extends C {
protected $b;
}
class A extends B {
private $a;
} Generated closure: $hydrateC = Closure::bind(function (array $data, C $object) {
$object->c = $values['c'];
}, null, C::class);
$hydrateB = Closure::bind(function (array $data, B $object) use ($hydrateC) {
$hydrateC->__invoke($data, $object);
$object->b = $values['b'];
}, null, B::class);
$hydrateA = Closure::bind(function (array $data, A $object) use ($hydrateB) {
$hydrateB->__invoke($data, $object);
$object->a = $values['a'];
}, null, A::class); Or alternate scenario where $hydrateB can be skipped: $hydrateC = Closure::bind(function (array $data, C $object) {
$object->c = $values['c'];
}, null, C::class);
$hydrateA = Closure::bind(function (array $data, A $object) use ($hydrateC) {
$hydrateC->__invoke($data, $object);
$object->b = $values['b'];
$object->a = $values['a'];
}, null, A::class); Hydration: $object = new A();
$data = ['a' => 1, 'b' => 2];
$hydrateA($data, $object); Notes:
General notes
|
Actually I partially implemented this in a fork of mine. My use case here is to keep backward compatibility and zend-hydrator interface, so in order to achieve this:
Here is a an exemple: /**
* This is a generated hydrator for the MakinaCorpus\Normalizer\Benchmarks\MockTextWithFormat class
*/
final class G402f10984f89322030223b7630346c10ce9bf0a4 implements HydratorInterface
{
public static $hydrate0;
public static $extract0;
public function hydrate(array $data, $object)
{
self::$hydrate0->__invoke($object, $data);
return $object;
}
public function extract($object)
{
$ret = [];
self::$extract0->__invoke($object, $ret);
return $ret;
}
}
G402f10984f89322030223b7630346c10ce9bf0a4::$hydrate0 = \Closure::bind(static function ($object, $values) {
if (isset($values['text']) || $object->text !== null && \array_key_exists('text', $values)) {
$object->text = $values['text'];
}
if (isset($values['format']) || $object->format !== null && \array_key_exists('format', $values)) {
$object->format = $values['format'];
}
}, null, 'MakinaCorpus\\Normalizer\\Benchmarks\\MockTextWithFormat');
G402f10984f89322030223b7630346c10ce9bf0a4::$extract0 = \Closure::bind(static function ($object, &$values) {
$values['text'] = $object->text;
$values['format'] = $object->format;
}, null, 'MakinaCorpus\\Normalizer\\Benchmarks\\MockTextWithFormat'); I still keep and use this fork for two reasons:
You can see this code in the master branch of the project: https://github.com/makinacorpus/generated-hydrator/blob/master/src/GeneratedHydrator/ClassGenerator/PHP5HydratorGenerator.php I intend to move the generated code to PHP7 in order to use ?? or ?: operators, I'm actually investigating this. |
Needless to say I would be very happy to reconcile my forked project with this one, so I can help if you wish. |
I've written a small PoC based on the ideas here today. https://gist.github.com/kelunik/3548c20696726cee2dbd69631b344557 Without code generation but direct closures instead, it's faster than the current code in my first benchmark.
|
As per discussions in #59, hydrator and extractor shouldn't be composed into a
zend-hydrator
, but should instead be two closures.An example API would be:
They would be used as following:
Composing them into a
Hydrator
instance that followszend-hydrator
spec is simple, but we'd also get a decent performance improvement by just relying on functional composition above.This would allow us to:
The text was updated successfully, but these errors were encountered: