Skip to content

Commit

Permalink
\#126: Fixes issues reading/writing typed properties which are uninit…
Browse files Browse the repository at this point in the history
…ialised and not passed in the data array
  • Loading branch information
Chris Riley committed May 25, 2023
1 parent 842e98f commit d2ffaf5
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,14 @@ private function generatePropertyHydrateCall(ObjectProperty $property, string $i
return ['$object->' . $propertyName . ' = ' . $inputArrayName . '[' . $escapedName . '] ?? null;'];
}

$nullCheck = ' || $object->' . $propertyName . ' !== null && \\array_key_exists(' . $escapedName . ', ' . $inputArrayName . ')';
if ($property->hasType) {
$nullCheck = ' || isset($object->' . $propertyName . ') && $object->' . $propertyName . ' !== null && \\array_key_exists(' . $escapedName . ', ' . $inputArrayName . ')';
}

return [
'if (isset(' . $inputArrayName . '[' . $escapedName . '])',
' || $object->' . $propertyName . ' !== null && \\array_key_exists(' . $escapedName . ', ' . $inputArrayName . ')',
$nullCheck,
') {',
' $object->' . $propertyName . ' = ' . $inputArrayName . '[' . $escapedName . '];',
'}',
Expand Down Expand Up @@ -141,7 +146,18 @@ private function replaceConstructor(ClassMethod $method): void
$bodyParts[] = '$this->extractCallbacks[] = \\Closure::bind(static function ($object, &$values) {';
foreach ($properties as $property) {
$propertyName = $property->name;
$bodyParts[] = " \$values['" . $propertyName . "'] = \$object->" . $propertyName . ';';
$requiresGuard = $property->hasType && !($property->hasDefault || $property->allowsNull);
$indent = $requiresGuard ? ' ' : ' ';

if ($requiresGuard) {
$bodyParts[] = ' if (isset($object->' . $propertyName . ')) {';
}

$bodyParts[] = $indent . "\$values['" . $propertyName . "'] = \$object->" . $propertyName . ';';

if ($requiresGuard) {
$bodyParts[] = ' }';
}
}

$bodyParts[] = '}, null, ' . var_export($className, true) . ');' . "\n";
Expand Down
24 changes: 24 additions & 0 deletions tests/GeneratedHydratorTest/Functional/HydratorFunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,30 @@ public function testHydratorWillNotRaisedUnitiliazedTypedPropertyAccessError():
], $hydrator->extract($instance));
}

/**
* Ensures that the hydrator will not attempt to read unitialized PHP >= 7.4
* typed property, which would cause "Uncaught Error: Typed property Foo::$a
* must not be accessed before initialization" PHP engine errors.
*
* @requires PHP >= 7.4
*/
public function testHydratorWillNotRaisedUnitiliazedTypedPropertyAccessErrorIfPropertyIsntHydrated(): void
{
$instance = new ClassWithTypedProperties();
$hydrator = $this->generateHydrator($instance);

$hydrator->hydrate(['untyped0' => 3], $instance);

self::assertSame([
'property0' => 1, // 'property0' has a default value, it should keep it.
'property1' => 2, // 'property1' has a default value, it should keep it.
'property3' => null, // 'property3' is not required, it should remain null.
'property4' => null, // 'property4' default value is null, it should remain null.
'untyped0' => 3, // 'untyped0' is null by default
'untyped1' => null, // 'untyped1' is null by default
], $hydrator->extract($instance));
}

/** @requires PHP >= 7.4 */
public function testHydratorWillSetAllTypedProperties(): void
{
Expand Down

0 comments on commit d2ffaf5

Please sign in to comment.