Skip to content

Commit

Permalink
[master] support public readonly properties, fixes #27 (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnnaDamm authored and BenMorel committed May 10, 2024
1 parent 6047560 commit fd0e05a
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 25 deletions.
32 changes: 16 additions & 16 deletions src/Internal/ObjectExporter/AnyObjectExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra
$returnNewObject = ($reflectionObject->getConstructor() === null);

while ($current) {
$publicProperties = [];
$nonPublicProperties = [];
$unsetPublicProperties = [];
$unsetNonPublicProperties = [];
$publicNonReadonlyProperties = [];
$nonPublicOrPublicReadonlyProperties = [];
$unsetPublicNonReadonlyProperties = [];
$unsetNonPublicOrPublicReadonlyProperties = [];

foreach ($current->getProperties() as $property) {
if ($property->isStatic()) {
Expand All @@ -70,26 +70,26 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra
if (array_key_exists($key, $objectAsArray)) {
$value = $objectAsArray[$key];

if ($property->isPublic()) {
$publicProperties[$name] = $value;
if ($property->isPublic() && !(method_exists($property, 'isReadOnly') && $property->isReadOnly())) {
$publicNonReadonlyProperties[$name] = $value;
} else {
$nonPublicProperties[$name] = $value;
$nonPublicOrPublicReadonlyProperties[$name] = $value;
}
} else {
if ($property->isPublic()) {
$unsetPublicProperties[] = $name;
if ($property->isPublic() && !(method_exists($property, 'isReadOnly') && $property->isReadOnly())) {
$unsetPublicNonReadonlyProperties[] = $name;
} else {
$unsetNonPublicProperties[] = $name;
$unsetNonPublicOrPublicReadonlyProperties[] = $name;
}
}

$returnNewObject = false;
}

if ($publicProperties || $unsetPublicProperties) {
if ($publicNonReadonlyProperties || $unsetPublicNonReadonlyProperties) {
$lines[] = '';

foreach ($publicProperties as $name => $value) {
foreach ($publicNonReadonlyProperties as $name => $value) {
/** @psalm-suppress RedundantCast See: https://github.com/vimeo/psalm/issues/4891 */
$name = (string) $name;

Expand All @@ -104,19 +104,19 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra
$lines = array_merge($lines, $exportedValue);
}

foreach ($unsetPublicProperties as $name) {
foreach ($unsetPublicNonReadonlyProperties as $name) {
$lines[] = 'unset($object->' . $this->escapePropName($name) . ');';
}
}

if ($nonPublicProperties || $unsetNonPublicProperties) {
if ($nonPublicOrPublicReadonlyProperties || $unsetNonPublicOrPublicReadonlyProperties) {
$closureLines = [];

if ($this->exporter->addTypeHints) {
$closureLines[] = '/** @var \\' . $current->getName() . ' $this */';
}

foreach ($nonPublicProperties as $name => $value) {
foreach ($nonPublicOrPublicReadonlyProperties as $name => $value) {
$newPath = $path;
$newPath[] = $name;

Expand All @@ -128,7 +128,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra
$closureLines = array_merge($closureLines, $exportedValue);
}

foreach ($unsetNonPublicProperties as $name) {
foreach ($unsetNonPublicOrPublicReadonlyProperties as $name) {
$closureLines[] = 'unset($this->' . $this->escapePropName($name) . ');';
}

Expand Down
9 changes: 2 additions & 7 deletions src/Internal/ObjectExporter/EnumExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@ class EnumExporter extends ObjectExporter
* {@inheritDoc}
*
* See: https://github.com/vimeo/psalm/pull/8117
* @psalm-suppress MixedInferredReturnType
* @psalm-suppress MixedReturnStatement
* @psalm-suppress RedundantCondition
*/
public function supports(\ReflectionObject $reflectionObject) : bool
{
if (! method_exists($reflectionObject, 'isEnum')) {
return false;
}

return $reflectionObject->isEnum();
return method_exists($reflectionObject, 'isEnum') && $reflectionObject->isEnum();
}

/**
Expand Down
12 changes: 12 additions & 0 deletions tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Brick\VarExporter\Tests\Classes;

class PublicReadonlyPropertiesWithoutConstructor
{
public readonly string $foo;
private readonly string $bar;
public string $baz;
}
15 changes: 15 additions & 0 deletions tests/Classes/ReadonlyPropertiesWithConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Brick\VarExporter\Tests\Classes;

class ReadonlyPropertiesWithConstructor
{
public function __construct(
public readonly string $foo,
private readonly string $bar,
public string $baz
) {
}
}
61 changes: 59 additions & 2 deletions tests/ExportObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

use Brick\VarExporter\Tests\Classes\ConstructorAndNoProperties;
use Brick\VarExporter\Tests\Classes\Enum;
use Brick\VarExporter\Tests\Classes\NoProperties;
use Brick\VarExporter\Tests\Classes\Hierarchy;
use Brick\VarExporter\Tests\Classes\PublicPropertiesWithConstructor;
use Brick\VarExporter\Tests\Classes\NoProperties;
use Brick\VarExporter\Tests\Classes\PrivateConstructor;
use Brick\VarExporter\Tests\Classes\PublicPropertiesOnly;
use Brick\VarExporter\Tests\Classes\PublicPropertiesWithConstructor;
use Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor;
use Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor;
use Brick\VarExporter\Tests\Classes\SerializeMagicMethods;
use Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor;
use Brick\VarExporter\Tests\Classes\SetState;
Expand Down Expand Up @@ -329,6 +331,61 @@ public function testExportClassWithPublicPropertiesAndConstructor(): void
$this->assertExportEquals($expected, $object);
}

/**
* @requires PHP 8.1
*/
public function testExportClassWithReadonlyPublicPropertiesAndConstructor(): void
{
$object = new ReadonlyPropertiesWithConstructor('public readonly', 'private readonly', 'public');

$expected = <<<'PHP'
(static function() {
$class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class);
$object = $class->newInstanceWithoutConstructor();
$object->baz = 'public';
(function() {
$this->foo = 'public readonly';
$this->bar = 'private readonly';
})->bindTo($object, \Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class)();
return $object;
})()
PHP;

$this->assertExportEquals($expected, $object);
}

/**
* @requires PHP 8.1
*/
public function testExportClassWithStateAndReadonlyPublicProperties(): void
{
$object = new PublicReadonlyPropertiesWithoutConstructor();

(function () {
$this->foo = 'foo';
})->bindTo($object, PublicReadonlyPropertiesWithoutConstructor::class)();

$expected = <<<'PHP'
(static function() {
$object = new \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor;
unset($object->baz);
(function() {
$this->foo = 'foo';
unset($this->bar);
})->bindTo($object, \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor::class)();
return $object;
})()
PHP;

$this->assertExportEquals($expected, $object);
}

public function testExportClassWithSerializeMagicMethods(): void
{
$object = new SerializeMagicMethods;
Expand Down

0 comments on commit fd0e05a

Please sign in to comment.