ocramius / generatedhydrator Goto Github PK
View Code? Open in Web Editor NEW:bullettrain_side: Fast Generated Object Hydrator for PHP
License: MIT License
:bullettrain_side: Fast Generated Object Hydrator for PHP
License: MIT License
Hello, @Ocramius.
I'm wondering what you think about implementing Doctrine GeneratedHydrator. I mean something like GeneratedHydrator extends \Doctrine\ORM\Internal\Hydration\AbstractHydrator
.
What do you think, is this a good idea? Will it have any advantages over ObjectHydrator?
Or maybe someone has already implemented it? :)
My dream is to learn how to fetch & hydrate & serialize large collections of entities with doctrine for an acceptable time.
For example I have these entities:
class Post {
/** @ORM\Id() */
private $id;
/** @ORM\Column(type="string") */
private $name;
/** @ORM\OneToMany(targetEntity="PostTag") */
private $postTags;
public function getTags(): Tag[]
{
return array_map(
function (PostTag $postTag) {
return $postTag->getTag();
},
$this->postTags->toArray()
)
}
}
class PostTag {
/** @ORM\ManyToOne(targetEntity="Post") */
private $post;
/** @ORM\ManyToOne(targetEntity="Tag") */
private $tag;
}
class Tag {
/** @ORM\Id() */
private $id;
/** @ORM\Column(type="string") */
private $name;
/** @ORM\OneToMany(targetEntity="PostTag") */
private $postTags;
...
public function getNameProcessedWithSomeKindOfMagic(): string
{
return processWithSomeKindOfMagic($this->name);
}
}
In my JSON API REST controller I want to do something like that:
class PostController {
public function getAllAction(): Response
{
$queryBuilder = $this->postRepository->createQueryBuilder('post')
->orderBy('post.id');
/** @var Post[] $posts */
$posts = $queryBuilder->getQuery()->getResult();
// Thx for 2-step hydration trick :)
// http://ocramius.github.io/blog/doctrine-orm-optimization-hydration/
$this->postRepository->createQueryBuilder('post')
->select('PARTIAL post.{id}')
->addSelect('postTag')
->addSelect('tag')
->leftJoin('post.postTags', 'postTag')
->leftJoin('postTag.tag', 'tag')
->getQuery()
->getResult();
// In this place I use Symfony Serializer with serialization mappings for each entity
// Normalized post will look like that example:
// $post = [
// 'id' => 1,
// 'name' => 'Foo Bar',
// 'tags' => [
// [
// 'id' => 1,
// 'name' => 'foo',
// 'name_processed_with_some_kind_of_magic' => 'magic_foo'
// ],
// [
// 'id' => 2,
// 'name' => 'bar',
// 'name_processed_with_some_kind_of_magic' => 'magic_bar'
// ]
// ],
// ];
$data = $this->serializer->serialize($posts);
return new JsonResponse($data, 200, [], true);
}
}
With few thousands posts in database this action will take SECONDS to generate JSON from database data and the only reason here will be complicated doctrine object hydration.
Switching to ArrayHydrator will reduce it to a couple of hundreds milliseconds, but I can't use it because entities is not a simple DTOs (look at getTags
and getNameProcessedWithSomeKindOfMagic
method examples). Database data denormalization and ArrayHydrator usage is also a pretty good solution in big collections API, but I wonder if its not the only solution.
My apologies for the fact that this longread is not directly related to your library, this is a really big problem for me and I will be infinitely glad for your ideas on this issue.
Thanks!
Hi @Ocramius,
I've been stuck in a dependency hell for weeks between this package (which we use in our project) and PHPStan/Rector (common point: they all depend on nikic/php-parser
, but different versions depending on the PHP version constraint). I've tried Docker'ing everything, using PHARs, and dark magic spells. But as our project is still on PHP 7.2 (and will be for some months - after all, EOL is 11.2020), nothing worked.
Then I looked up the history of this package and noticed that the version constraint was bumped from 7.2 to 7.3 in this commit from this PR. I've looked at the diff (and the one between 2.2.0
- compatible 7.2 - and 3.0
- requiring 7.3 -) and I can't for the sake of me find a single change that requires PHP 7.3. Which begs the question : why was the version constraint bumped in the first place ? (currently supposition : "because we can" ? ... ๐ )
If so, I'd like to make a point for all those who can't upgrade just yet and/or would hugely benefits using tools like Rector to do so. So, could we revert this constraint back to 7.2 ?
Thanks for your consideration - and hopefully your help getting me out of this nightmare.
gnutix
I just hit the roof of file (not path) length on my fs (encrypted ext4 = 143 bytes):
AppHydrator__PM__ThingContainerYToxOntzOjc6ImZhY3RvcnkiO3M6NDE6IkdlbmVyYXRlZEh5ZHJhdG9yXEZhY3RvcnlcSHlkcmF0b3JGYWN0b3J5Ijt9.php.5d2dc0a9162c80.87561400
Its 151 bytes, without namespaces.
While in production it will be 255 bytes, maybe a control over entropy is a good idea?
Ah I see. Its the encoded params base64-encoded via NameInflector. Damn.
Hello Guys, i have a question about collection hydration, since my collections include other domain objects.
Lets say that i have Car
Domain Model with has a collection of Maintenance Logs
which include Log
Domain Model, and each has its own Hydrator
when i call CarHydrator->hydrate
will it use the LogHydrator
automatically, or do i have to configure it in some way ?
When reading private class members, we usually do a function call per-property:
$prop1 = Closure::bind(function ($o) { return $o->prop1; }, null, 'ClassName');
$prop2 = Closure::bind(function ($o) { return $o->prop2; }, null, 'ClassName');
return array(
'prop1' => $prop1($object),
'prop2' => $prop2($object),
);
If prop1
and prop2
have the same declaring class, the method call can be simply one:
$props = Closure::bind(function ($o) {
return array($o->prop1, $o->prop2);
}, null, 'ClassName');
list($prop1, $prop2) = $props($object);
return array(
'prop1' => $prop1,
'prop2' => $prop2,
);
Same could be done with just arrays:
$data = $props($object);
return array(
'prop1' => $data[0],
'prop2' => $data[1],
);
Comparison still to be done, but list
may be faster
Hello,
Here is how I use this hydrator
But I have a problem with nullable properties, indeed the hydrator will hydrate the null value for these properties, if they are not present in the data array.
I am able to correct the problem by modifying the HydratorMethodsVisitor class at line 110 by replacing
return ['$ object->'. $ propertyName. '='. $ inputArrayName. '['. $ escapedName. '] ?? null; '];
by
return ['$ object->'. $ propertyName. '='. $ inputArrayName. '['. $ escapedName. '] ?? '. '$ object->'. $ propertyName. ' ?? null; '];
So my question is, shouldn't the hydrator only hydrate properties if it exists in the array, like Laminas Hydrator ?
Should I use my own FileWriterGeneratorStrategy to overcome this problem ?
Thanks
I've tried to use generated hydrators in my new project (https://github.com/matthiasnoback/broadway-serialization) but ran into a bit of trouble: when hydrating an object the data array needs to have a value for each property of the object. But in the case of, for example, event objects serialized and stored in an event store, some properties may have been added in the class of the event, but not in the data array. So an extra call to array_key_exists()
would be required in this case.
Do you have any suggestions on how to fix this problem? I assume, based on other issues that have been reported, that you are not going to add any runtime checks. But if I want to, how should I proceed?
It seems very normal in the end, since that readonly properties can only be initialized from constructor, but since I mostly bypass constructors to hydrate all object values at once, I end up on this error:
Cannot initialize readonly property App\Domain\Bibliotheque\Model\ContenuBibliotheque::$id from scope GeneratedHydratorGeneratedClass\__PM__\App\Domain\Bibliotheque\Model\ContenuBibliotheque\YToxOntzOjc6ImZhY3RvcnkiO3M6NDE6IkdlbmVyYXRlZEh5ZHJhdG9yXEZhY3RvcnlcSHlkcmF0b3JGYWN0b3J5Ijt9
I wonder if there's a trick to bypass this ?
I am opening this issue just to make sure noone ever tries to do anything bad with the reflection part of generation.
I did some benchmarking as I liked the built-in filtering of reflection properties than filtering the array of them. Turned out it is much slower.
Benchmark (using athletic):
<?php
class Example extends \Athletic\AthleticEvent
{
protected $class;
public function setUp()
{
$this->class = new LongExampleWithValues;
}
/**
* @iterations 1000
*/
public function testBuiltInFiltering()
{
$reflection = new \ReflectionClass($this->class);
$reflectionProperties = array_diff(
$reflection->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED),
$reflection->getProperties(\ReflectionProperty::IS_STATIC)
);
}
/**
* @iterations 1000
*/
public function testArrayFiltering()
{
$reflection = new \ReflectionClass($this->class);
$reflectionProperties = array_filter(
$reflection->getProperties(),
function (\ReflectionProperty $property) {
return ($property->isPublic() || $property->isProtected()) && ! $property->isStatic();
}
);
}
}
Test class:
<?php
class LongExampleWithValues
{
private $a = 'a';
protected $b = null;
public $c = 1;
static $d = true;
public $e = -1;
public $f = 'F';
public $g = 1.0;
public $h = -1.0;
public $i = PHP_INT_MAX;
public $j;
}
Results:
Example
Method Name Iterations Average Time Ops/second
-------------------- ------------ -------------- -------------
testBuiltInFiltering: [1,000 ] [0.0000722653866] [13,837.88349]
testArrayFiltering : [1,000 ] [0.0000248982906] [40,163.39976]
@Ocramius you are free to close this, just wanted to share some experience.
I get wanting to implement the cool new features of PHP but how about a little backwards compatibility? I work with a company that JUST updated PHP to 7.2 (took months to migrate all the code with 3 programmers).
Going back into the commits and grabbing an older version I guess.
You suggested a very primitive benchmark in the README.md file, but it's actually not very significant. Indeed, your API is much faster, but it does not take into account private and protected properties hydration, as well as it forgots pretty much everything about class hierarchy.
In some cases, array keys provided to the hydrator may not correspond with what the object internal state looks like.
The hydrator should therefore be configurable, by allowing:
The current generated hydrator has the signature:
class Example {
private $a;
protected $b;
public $c;
}
class Generated extends Example implements HydratorInterface
{
private $a = null;
protected $b = null;
public $c = null;
function __construct()
{
$this->aWriter579676c837a6e801650765 = \Closure::bind(function ($object, $value) {
$object->a = $value;
}, null, 'Example');
}
function hydrate(array $data, $object)
{
$object->b = $data['b'];
$object->c = $data['c'];
$this->aWriter579676c837a6e801650765->__invoke($object, $data['a']);
return $object;
}
// ... and extract()
}
But with PHP7 this can be simplified some :)
class Generated extends Example implements HydratorInterface
{
private $a = null;
protected $b = null;
public $c = null;
function __construct()
{
}
function hydrate(array $data, $object)
{
$object->b = $data['b'];
$object->c = $data['c'];
(function($data) {
$object->a = $data['a'];
})->call($object, $data);
return $object;
}
// ... and extract()
}
Closure::call
does not return a new closure like Closure::bind
does, but simply temporarily binds it and immediately calls it. Some overhead is saved this way, and it is probably faster. Haven't tested it though, but wanted to mention it here anyway.
And isn't it an idea to wrap hydrate's body in a closure by default? Probably most of the time at least some data members are private.
function hydrate(array $data, $object)
{
(function($data) {
$this->a = $data['a'];
$this->b = $data['b'];
$this->c = $data['c'];
})->call($object, $data);
}
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These problems occurred while renovating this repository. View logs.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
laminas/laminas-ci-matrix-action
, laminas/laminas-continuous-integration-action
, laminas/laminas-hydrator
, nikic/php-parser
, phpbench/phpbench
, phpunit/phpunit
, psalm/plugin-phpunit
, roave/infection-static-analysis-plugin
, shivammathur/setup-php
, vimeo/psalm
)composer.json
php ~8.1.0 || ~8.2.0 || ~8.3.0
laminas/laminas-hydrator ^4.14.0
nikic/php-parser ^4.16.0
ocramius/code-generator-utils ^1.7.0
doctrine/coding-standard ^12.0.0
phpbench/phpbench ^1.2.14
phpunit/phpunit ^9.6.10
psalm/plugin-phpunit ^0.18.4
roave/infection-static-analysis-plugin ^1.32.0
vimeo/psalm ^5.13.1
.github/workflows/continuous-integration.yml
laminas/laminas-ci-matrix-action 1.22.1
laminas/laminas-continuous-integration-action 1.32.0
actions/checkout v4
shivammathur/setup-php 2.25.4
actions/cache v3
.github/workflows/release-on-milestone-closed.yml
actions/checkout v4
laminas/automatic-releases v1
laminas/automatic-releases v1
laminas/automatic-releases v1
laminas/automatic-releases v1
laminas/automatic-releases v1
ProxyManager includes a lot of logic that is very useful to generate classes at runtime, but that logic should be removed from the main repository so that this package doesn't depend on it.
Hydrator strategies are really flexible, but should be compiled into a generated hydrator too
EDIT: I am so sorry, I was wrong, bug is elsewhere, I did too much code reading without actually testing before...
Hello, following #118 I stumbled upon yet another PHP 7.4 side effect.
I'm not sure this is really a bug, so I open this issue for discussion.
Considering the following PHP class:
namespace Some\Namespace;
class Foo
{
public int $bar;
}
GeneratedHydrator will generate the following code:
class SomeGeneratedNameForHydrator implements \Zend\Hydrator\HydratorInterface
{
private $hydrateCallbacks = array(), $extractCallbacks = array();
function __construct()
{
$this->hydrateCallbacks[] = \Closure::bind(static function ($object, $values) {
if (isset($values['bar']) || $object->bar !== null && \array_key_exists('bar', $values)) {
$object->bar = $values['bar'];
}
}, null, 'Some\\Namespace\\Foo');
$this->extractCallbacks[] = \Closure::bind(static function ($object, &$values) {
$values['bar'] = $object->bar;
}, null, 'Some\\Namespace\\Foo');
}
function hydrate(array $data, $object)
{
$this->hydrateCallbacks[0]->__invoke($object, $data);
return $object;
}
function extract($object)
{
$ret = array();
$this->extractCallbacks[0]->__invoke($object, $ret);
return $ret;
}
}
If I attempt an hydration, this will cause the following error:
"Typed property Some\Namespace\Foo::$bar must not be accessed before initialization"
Problem is that the following line:
if (isset($values['bar']) || $object->bar !== null && \array_key_exists('bar', $values)) {
$object->bar = $values['bar'];
}
Will cause that.
Since that $bar
is mandatory, and has no default, we could safely write:
if (isset($values['bar'])) {
$object->bar = $values['bar'];
}
instead, and that would work.
Now, I cannot see any regression because of this code let me explain: let's consider the use case I'm working with, I always hydrate empty objects, created without a constructor, the following way:
$ref = new \ReflectionClass(Some\Namespace\Foo::class);
$object = $ref->newInstanceWithoutConstructor();
$class = $someHydratorFactory->getHydratorClass();
(new $class())->hydrate($object, [
'bar' => 12,
]);
In this case, everything would be fine with the proposed generated code modification.
For other use cases such as:
namespace Some\Namespace;
class Foo
{
public int $bar;
public function __construct(int $bar)
{
$this->bar = $bar;
}
}
Then:
$object = new Some\Namespace\Foo(7);
$class = $someHydratorFactory->getHydratorClass();
(new $class())->hydrate($object, [
'bar' => 12,
]);
Should work as well, meaning that $bar
value after hydration would be 12 as well.
If I remeber correctly, $object->bar !== null && \array_key_exists('bar', $values)
was added so that null
value can be explicitely set by the hydrator.
Devil here is in null
handling, in this case (non nullable property), an explicit null value would be erroneous since that the target property can not be null and raise an error while attempt to set it explicitely. I guess we can safely remove the null handling then.
I can see one use case we miss: if the user do this:
$object = new Some\Namespace\Foo(7);
$class = $someHydratorFactory->getHydratorClass();
(new $class())->hydrate($object, [
'bar' => null,
]);
Then isset($values['bar'])
will miss that value is here, and is null. Should we do some kind of validation here, such as:
if (\array_key_exists($values['bar'])) {
if (null === $values['bar']) {
throw new HydratorKindOfException("Some\Namespace\Foo::\$bar cannot be null");
}
$object->bar = $values['bar'];
}
Or just generate:
if (\array_key_exists($values['bar'])) {
$object->bar = $values['bar'];
}
And let PHP raise the exception itself? I think this option is the best, \array_key_exists()
has now a specific OPCode for itself, and the performance impact would be, I guess, meaningless. Maybe it needs testing thought, but semantically, this is most accurate solution, and it solves all use cases altogether.
I will open a PR with tests and the modified code.
I'm trying to install drupal using composer.
Here's an error I get:
Problem 1
- ocramius/generated-hydrator 1.1.1 requires php ~5.4 -> your PHP version (7.0.4) does not satisfy that requirement.
- ocramius/generated-hydrator 1.1.1 requires php ~5.4 -> your PHP version (7.0.4) does not satisfy that requirement.
- ocramius/generated-hydrator 1.1.1 requires php ~5.4 -> your PHP version (7.0.4) does not satisfy that requirement.
- Installation request for ocramius/generated-hydrator (locked at 1.1.1, required as ^1.1) -> satisfiable by ocramius/generated-hydrator[1.1.1].
CodeGenerationUtils
is currently bundled as it is not stable yet. Should be moved to an own repository.
Depends on facebook/hhvm#1203
@Ocramius and others, I wonder what your opinion is about metadata for hydrator generation.
For example, I'd like to see integration for hydrator strategies or nesting hydrators, but it seems to me that you'd need to have some kind of metadata about how to map the array to the object. Thinks like, for example:
['bar' => ['quu' => 'quuz']]
arary, hydrate to a Foo object that has a Bar object as property. The FooHydrator would need to call (or inline) the BarHydrator.I think some kind of metadata is required to support these use cases, but I don't think you'd want to replicate a large part of e.g. JMS/Serializer. The point of generated hydrators is to be very, very fast.
What would be a sane way to go about this?
I just ran into #69 but with nikic/php-parser v4, which was released in March.
I'm using phpstan - phpstan/phpstan: 0.10
requires nikic/php-parser` v4...
FWIW:
composer update -v
didn't upgrade nikic/php-parser
because ocramius/code-generator-utils
uses v3.nikic/php-parser
v4 directly into ./vendor
but the tests failed with Class 'PhpParser\BuilderAbstract' not found
.It might be a bit of a job.
I'll look at downgrading phpstan
to 0.9.2
to keep going.
Hi,
I've tried to extract an object that contains extract()
or hydrate()
methods in them. So basically, using the docs example it would be sth like:
class Example
{
public $foo = 1;
protected $bar = 2;
protected $baz = 3;
public function extract() {
// anything here
}
}
$config = new Configuration('Example');
$hydratorClass = $config->createFactory()->getHydratorClass();
$hydrator = new $hydratorClass();
$object = new Example();
var_dump($hydrator->extract($object)); // PHP STRICT ERROR - extract() method interface is wrong
I presume this is because the usage of Visitor pattern in HydratorMethodsVisitor
. I think it's a critial design part of this hydrator so I'd rather ask to patch up the docs mentioning this as a limitation. My final argument is that GeneratedHydrator works as Reflection
hydrator replacement, but Reflection
doesn't fail on this case.
EDIT: Of course, patching Example::extract($object)
interface to match number of arguments works, but it's a nasty workaround.
This issue is a placeholder for the next minor release: I've yet to write a detailed reasoning for why I want to abandon this project.
The TLDR is:
doctrine/orm
internals
doctrine/orm
Therefore there won't be PHP 8.4 support for this library, which will instead be abandoned.
TODO: adjust README.md
with architectural reasoning behind this library, and mark it security-only for a while.
Abandon later, once PHP 8.3 is EOL.
Just making sure you are aware of that. Because issets are used in generated hydrators instead of array_key_exists, these hydrators are unable to override default property value with a null for non-public properties.
So that the factory is a real one :)
Currently the hydrator overwrites every property in an object, even if they are not present in the data array. The hydrator shouldn't overwrite these properties.
class ExampleDTO {
public int $foo;
protected string $bar;
/** @var bool[] */
protected $baz;
}
$config = new Configuration('ExampleDTO');
$hydratorClass = $config->createFactory()->getHydratorClass();
$hydrator = new $hydratorClass();
$hydrator->hydrate(
['foo' => '150', 'bar' => 5, 'baz' => [1,0,0]],
$object = new ExampleDTO()
);
var_dump($object);
baz
array isn't getting converted to bools. Is there a proper way to hint this that will enforce the casting of baz
to bool[]
?
First of all, I'm not sure whether this is a bug, an expected behaviour or a missing feature, so please go easy :)
I try to use pre-generating hydrators feature and hook it up with my deployment system. I've created generate.php
script with a snippet doing basically what documentation says (and it works fine). I, then, intend to use it with pre-autoload-dump
composer event script in order to achieve fresh hydrators for my objects every time I run composer install/update
(regardless if it's development or production env).
The reason I want fresh hydrators every time is that the default values / attributes can easily change during development.
Because of a class existence check in HydratorFactory::getHydratorClass()
, HydratorFactory
will not attempt to create a proxy class when it already exists. I think pre-generation is intended for production usage only, when classes are not meant to get changed (thus hydrators don't need refreshing). Anyway, removing this class existence check makes it fit my scenario. How do you feel about forceRefresh
kind of flag @Ocramius ?
Alternatively, do you have any ideas how to work it around ?
I have been investigating an issue which is causing invalid proxies to be generated, and it appears to be associated with classes in namespaces starting with a "v" prefix, e.g. v1.
Example code:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use GeneratedHydrator\Configuration;
$object = new \AcmeBank\Services\BankingService\Core\Contracts\v1\Broken('code');
$config = new Configuration(get_class($object));
$hydratorClass = $config->createFactory()->getHydratorClass();
$hydrator = new $hydratorClass();
$extracted = $hydrator->extract($object);
var_dump($extracted);
During the $hydrator->extract($object)
call, the following error occurs:
PHP Notice: Undefined index: in /private/var/folders/fv/r2fq3kk54rb6g1ycxwpcyf4svg09tw/T/GeneratedHydratorGeneratedClass__PM__AcmeBankServicesBankingServiceCoreContractsv1BrokenYToxOntzOjc6ImZhY3RvcnkiO3M6NDE6IkdlbmVyYXRlZEh5ZHJhdG9yXEZhY3RvcnlcSHlkcmF0b3JGYWN0b3J5Ijt9.php on line 27
Here's a look at the generated proxy:
<?php
namespace GeneratedHydratorGeneratedClass\__PM__\AcmeBank\Services\BankingService\Core\Contracts\v1\Broken;
class YToxOntzOjc6ImZhY3RvcnkiO3M6NDE6IkdlbmVyYXRlZEh5ZHJhdG9yXEZhY3RvcnlcSHlkcmF0b3JGYWN0b3J5Ijt9 extends \AcmeBank\Services\BankingService\Core\Contracts\v1\Broke$
{
private $institutionCode = null;
private $name = null;
function __construct()
{
$this->institutionCodeWriter56e1c00865d30311291394 = \Closure::bind(function ($object, $value) {
$object->institutionCode = $value;
}, null, 'AcmeBank\\Services\\BankingService\\Core\\Contracts\\v1\\Broken');
$this->nameWriter56e1c00865d7b109427712 = \Closure::bind(function ($object, $value) {
$object->name = $value;
}, null, 'AcmeBank\\Services\\BankingService\\Core\\Contracts\\v1\\Broken');
}
function hydrate(array $data, $object)
{
$this->institutionCodeWriter56e1c00865d30311291394->__invoke($object, $data['institutionCode']);
$this->nameWriter56e1c00865d7b109427712->__invoke($object, $data['name']);
return $object;
}
function extract($object)
{
$data = (array) $object;
return array('institutionCode' => $data['^@AcmeBank\\Services\\BankingService\\Core\\Contracts^K1\\Broken^@institutionCode'], 'name' => $data['^@AcmeBank\\S$
}
}
If you take a look at the extract
method in the proxy, you'll notice that instead of the v1
namespace, the string ^K1
is being included. It's not clear if this is an issue in PhpParser or GeneratedHydrator, but I've begun examining the source to determine where the problem is occurring.
I would be happy to provide a sample project to ease reproduction of the issue if needed.
I got the following error message when I ran the example code in the PHP7.4 environment.
ParseError : syntax error, unexpected '|', expecting ';' or '{'
I think the reason is that it requires "ocramius/code-generator-utils": "^1.0.0" which requires "php": "^8.0",
I noticed this property called privateProperty
is actually declared as protected. https://github.com/Ocramius/GeneratedHydrator/blob/master/tests/GeneratedHydratorTestAsset/BaseClass.php#L42
Hi @Ocramius,
Are you planning to upgrade nikic/php-parser
dependency to use version 3? My project already uses nikic/php-parser
, but requires version 3 which conflicts with version 2 used by ocramius/generated-hydrator
.
With PHP 5.6, chicken and eggs issue:
Using
"require": {
"zendframework/zendframework": "^3.0",
"ocramius/generated-hydrator": "^2.0"
},
- ocramius/generated-hydrator 2.0.0 requires php ~7.0 -> your PHP version (5.6.23) does not satisfy that requirement.
Seems expected, so trying using previous version
"require": {
"zendframework/zendframework": "^3.0",
"ocramius/generated-hydrator": "^1.2"
},
Can't be installed as requires zend-stdlib 2
Copy-paste first example from readme.
Edit HydratorFactory
class (we will add debug dumping for $hydratorClassName
variable):
public function getHydratorClass() : string
{
$inflector = $this->configuration->getClassNameInflector();
$realClassName = $inflector->getUserClassName($this->configuration->getHydratedClassName());
$hydratorClassName = $inflector->getGeneratedClassName($realClassName, ['factory' => get_class($this)]);
var_export(class_exists($hydratorClassName));
if (! class_exists($hydratorClassName) && $this->configuration->doesAutoGenerateProxies()) {
$generator = $this->configuration->getHydratorGenerator();
$originalClass = new ReflectionClass($realClassName);
$generatedAst = $generator->generate($originalClass);
$traverser = new NodeTraverser();
$traverser->addVisitor(new ClassRenamerVisitor($originalClass, $hydratorClassName));
$this->configuration->getGeneratorStrategy()->generate($traverser->traverse($generatedAst));
$this->configuration->getGeneratedClassAutoloader()->__invoke($hydratorClassName);
}
return $hydratorClassName;
}
cd /path/to/tmp
Run:
class_exists === false
;class_exists === false
;Class not found, so how not added to autoloader.
This fix solve problem:
public function getHydratorClass() : string
{
$inflector = $this->configuration->getClassNameInflector();
$realClassName = $inflector->getUserClassName($this->configuration->getHydratedClassName());
$hydratorClassName = $inflector->getGeneratedClassName($realClassName, ['factory' => get_class($this)]);
// FIX.
$this->configuration->getGeneratedClassAutoloader()->__invoke($hydratorClassName);
if (! class_exists($hydratorClassName) && $this->configuration->doesAutoGenerateProxies()) {
$generator = $this->configuration->getHydratorGenerator();
$originalClass = new ReflectionClass($realClassName);
$generatedAst = $generator->generate($originalClass);
$traverser = new NodeTraverser();
$traverser->addVisitor(new ClassRenamerVisitor($originalClass, $hydratorClassName));
$this->configuration->getGeneratorStrategy()->generate($traverser->traverse($generatedAst));
$this->configuration->getGeneratedClassAutoloader()->__invoke($hydratorClassName);
}
return $hydratorClassName;
}
The commit @c0549d5 can be reverted once the next ZF2 release is out
As of Ocramius/ProxyManager#62 and http://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/ , runtime reflection instantiation can be removed in favour of Closure::bind()
based scope changes
I'm getting NULL for nested objects, is it possible do use this recursively?
Generated Hydrator fails with static Properties.
Solution: Ignore Static Properties
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:
interface GenerateHydrator
{
public function __invoke(string $className) : callable;
}
interface GenerateExtractor
{
public function __invoke(string $className) : callable;
}
They would be used as following:
$object = $generateHydrator->__invoke(get_class($object))($object, $data);
$data = $generateExtractor->__invoke(get_class($object))($object);
Composing them into a Hydrator
instance that follows zend-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:
As of current implementation, GeneratedHydrator will not distinguish between properties from following
example:
class Foo
{
private $bar;
}
class Bar extends Foo
{
private $bar;
}
class Baz extends Foo
{
private $bar;
}
For example, in hydrate methods, this:
if (isset($values['current_revision']) || $object->current_revision !== null && \array_key_exists('current_revision', $values)) {
$object->current_revision = $values['current_revision'];
}
will raise a "property cannot be accessed prior to initialization" if it's typed and required, because of the object->current_revision !== null
statement.
This means that the generated code must vary depending on how the property was declared, for typed and required properties, default value must be determined during compilation and code must be generated otherwise.
For example, let's consider that all other use case keep the same code, but for typed required properties, if property has a default value:
if (isset($values['current_revision']) || \array_key_exists('current_revision', $values)) {
$object->current_revision = $values['current_revision'];
}
Or:
$object->current_revision = $values['current_revision'] ?? $object->current_revision;
And if it has none:
if (isset($values['current_revision'])) {
$object->current_revision = $values['current_revision'];
}
Or:
$object->current_revision = $values['current_revision'] ?? null;
Which also could be a runtime optimization (for the later) whereas it could be slower (for the former) but since now recent PHP versions have an optimised opcode for \array_key_exists()
this probably wouldn't be visible.
Hi there,
Would like to ask if there is a planned support for PHP 8.3 anytime soon?
(as currently php is locked into ~8.1.0 || ~8.2.0)
Reason: Allowing codebase migration to php 8.3, thus having no composer issues with dependency tree requirements
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.