Skip to content

Commit c50bfa8

Browse files
authored
Fixed screen constructor property initialization during deserialization (#2885)
* Fixed screen constructor property initialization during deserialization * Fixed code style --------- Co-authored-by: tabuna <[email protected]>
1 parent d1f32e9 commit c50bfa8

File tree

5 files changed

+458
-2
lines changed

5 files changed

+458
-2
lines changed

src/Screen/Concerns/ModelStateRetrievable.php

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
namespace Orchid\Screen\Concerns;
44

5-
use Illuminate\Queue\SerializesModels;
5+
use Illuminate\Queue\Attributes\WithoutRelations;
6+
use Illuminate\Queue\SerializesAndRestoresModelIdentifiers;
67

78
/**
89
* This trait is designed for managing the state of Eloquent models. It uses
@@ -14,5 +15,116 @@
1415
*/
1516
trait ModelStateRetrievable
1617
{
17-
use SerializesModels;
18+
use SerializesAndRestoresModelIdentifiers;
19+
20+
/**
21+
* Prepare the instance values for serialization.
22+
*
23+
* @return array
24+
*/
25+
public function __serialize()
26+
{
27+
$values = [];
28+
29+
$reflectionClass = new \ReflectionClass($this);
30+
31+
[$properties, $classLevelWithoutRelations] = [
32+
$reflectionClass->getProperties(),
33+
! empty($reflectionClass->getAttributes(WithoutRelations::class)),
34+
];
35+
36+
foreach ($properties as $property) {
37+
if ($property->isStatic()) {
38+
continue;
39+
}
40+
41+
if (! $property->isInitialized($this)) {
42+
continue;
43+
}
44+
45+
$value = $this->getPropertyValue($property);
46+
47+
if ($property->hasDefaultValue() && $value === $property->getDefaultValue()) {
48+
continue;
49+
}
50+
51+
$name = $property->getName();
52+
53+
if ($property->isPrivate() || $property->isProtected()) {
54+
continue;
55+
}
56+
57+
$values[$name] = $this->getSerializedPropertyValue(
58+
$value,
59+
! $classLevelWithoutRelations &&
60+
empty($property->getAttributes(WithoutRelations::class))
61+
);
62+
}
63+
64+
return $values;
65+
}
66+
67+
/**
68+
* Restore the model after serialization.
69+
*
70+
* @param array $values
71+
*
72+
* @throws \Illuminate\Contracts\Container\BindingResolutionException
73+
* @throws \ReflectionException
74+
*
75+
* @return void
76+
*/
77+
public function __unserialize(array $values)
78+
{
79+
$default = $this->getDefaultPropertyWithConstructor();
80+
81+
$values = array_merge($default, $values);
82+
83+
$properties = (new \ReflectionClass($this))->getProperties();
84+
85+
foreach ($properties as $property) {
86+
if ($property->isStatic()) {
87+
continue;
88+
}
89+
90+
$name = $property->getName();
91+
92+
if (! array_key_exists($name, $values)) {
93+
continue;
94+
}
95+
96+
$property->setValue(
97+
$this, $this->getRestoredPropertyValue($values[$name])
98+
);
99+
}
100+
}
101+
102+
/**
103+
* Get the property value for the given property.
104+
*
105+
* @param \ReflectionProperty $property
106+
*
107+
* @return mixed
108+
*/
109+
protected function getPropertyValue(\ReflectionProperty $property)
110+
{
111+
return $property->getValue($this);
112+
}
113+
114+
/**
115+
* @throws \Illuminate\Contracts\Container\BindingResolutionException
116+
* @throws \ReflectionException
117+
*
118+
* @return array
119+
*/
120+
protected function getDefaultPropertyWithConstructor(): array
121+
{
122+
$default = app()->make(static::class);
123+
124+
$defaultReflection = (new \ReflectionClass($default))->getProperties();
125+
126+
return collect($defaultReflection)
127+
->mapWithKeys(fn (\ReflectionProperty $property) => [$property->getName() => $property->getValue($default)])
128+
->all();
129+
}
18130
}

src/Screen/Screen.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,4 +472,67 @@ public function frontendController(): string
472472
{
473473
return 'base';
474474
}
475+
476+
/**
477+
* Reinitialized uninitialized properties with their default values.
478+
*
479+
* @throws \Illuminate\Contracts\Container\BindingResolutionException
480+
* @throws \ReflectionException
481+
*/
482+
public function __wakeup(): void
483+
{
484+
// Create a fresh instance to retrieve default property values
485+
$defaultInstance = app()->make(static::class);
486+
487+
$defaultProperties = $this->getPropertiesWithValues($defaultInstance);
488+
$currentProperties = (new \ReflectionClass($this))->getProperties();
489+
490+
foreach ($currentProperties as $property) {
491+
// Skip if the property is already initialized
492+
if ($property->isInitialized($this)) {
493+
continue;
494+
}
495+
496+
$propertyName = $property->getName();
497+
498+
// Skip if there's no default value for the property
499+
if (! $defaultProperties->has($propertyName)) {
500+
continue;
501+
}
502+
503+
// Set the property to its default value
504+
$property->setValue($this, $defaultProperties->get($propertyName));
505+
}
506+
}
507+
508+
/**
509+
* Returns an associative array of property names and their values for the given object.
510+
*
511+
* @param object $object
512+
*
513+
* @throws \ReflectionException
514+
*
515+
* @return Collection
516+
*/
517+
private function getPropertiesWithValues(object $object): Collection
518+
{
519+
$reflection = new \ReflectionClass($object);
520+
521+
return collect($reflection->getProperties())
522+
->mapWithKeys(function (\ReflectionProperty $property) use ($object) {
523+
$property->setAccessible(true); // Ensure we can access private/protected properties
524+
525+
return [$property->getName() => $property->getValue($object)];
526+
});
527+
}
528+
529+
/**
530+
* Returns an array of public property names for serialization.
531+
*
532+
* @return array
533+
*/
534+
public function __sleep(): array
535+
{
536+
return $this->getPublicPropertyNames()->all();
537+
}
475538
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Orchid\Tests\App\Screens;
6+
7+
use Illuminate\Foundation\Application;
8+
use Orchid\Screen\Concerns\ModelStateRetrievable;
9+
use Orchid\Screen\Screen;
10+
11+
class SerializeRetrievableScreen extends Screen
12+
{
13+
use ModelStateRetrievable;
14+
15+
public $public = 'Public';
16+
17+
public function __construct(
18+
protected Application $application,
19+
private readonly string $private = 'Private',
20+
public $user = null
21+
) {
22+
$this->middleware(function ($request, $next) {
23+
return $next($request);
24+
});
25+
}
26+
27+
/**
28+
* Query data.
29+
*/
30+
public function query(): array
31+
{
32+
return [];
33+
}
34+
35+
/**
36+
* Display header name.
37+
*/
38+
public function name(): ?string
39+
{
40+
return 'Route Resolve Screen';
41+
}
42+
43+
/**
44+
* Display header description.
45+
*/
46+
public function description(): ?string
47+
{
48+
return 'Test screen';
49+
}
50+
51+
/**
52+
* Button commands.
53+
*
54+
* @return \Orchid\Screen\Action[]
55+
*/
56+
public function commandBar(): array
57+
{
58+
return [];
59+
}
60+
61+
/**
62+
* Views.
63+
*
64+
* @return \Orchid\Screen\Layout[]
65+
*/
66+
public function layout(): array
67+
{
68+
return [];
69+
}
70+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Orchid\Tests\App\Screens;
6+
7+
use Illuminate\Foundation\Application;
8+
use Orchid\Screen\Screen;
9+
10+
class SerializeScreen extends Screen
11+
{
12+
public $public = 'Public';
13+
14+
public function __construct(
15+
protected Application $application,
16+
private readonly string $private = 'Private',
17+
public $user = null
18+
) {
19+
$this->middleware(function ($request, $next) {
20+
return $next($request);
21+
});
22+
}
23+
24+
/**
25+
* Query data.
26+
*/
27+
public function query(): array
28+
{
29+
return [];
30+
}
31+
32+
/**
33+
* Display header name.
34+
*/
35+
public function name(): ?string
36+
{
37+
return 'Route Resolve Screen';
38+
}
39+
40+
/**
41+
* Display header description.
42+
*/
43+
public function description(): ?string
44+
{
45+
return 'Test screen';
46+
}
47+
48+
/**
49+
* Button commands.
50+
*
51+
* @return \Orchid\Screen\Action[]
52+
*/
53+
public function commandBar(): array
54+
{
55+
return [];
56+
}
57+
58+
/**
59+
* Views.
60+
*
61+
* @return \Orchid\Screen\Layout[]
62+
*/
63+
public function layout(): array
64+
{
65+
return [];
66+
}
67+
}

0 commit comments

Comments
 (0)