Skip to content
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

[master] support public readonly properties, fixes #27 #28

Merged
merged 7 commits into from
Jan 21, 2023
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