@@ -29,16 +29,23 @@ trait LazyGhostTrait
2929 * properties and closures should accept 4 arguments: the instance to
3030 * initialize, the property to initialize, its write-scope, and its default
3131 * value. Each closure should return the value of the corresponding property.
32+ * The special "\0" key can be used to define a closure that returns all
33+ * properties at once when full-initialization is needed; it takes the
34+ * instance and its default properties as arguments.
3235 *
3336 * Properties should be indexed by their array-cast name, see
3437 * https://php.net/manual/language.types.array#language.types.array.casting
3538 *
36- * @param \Closure(static):void|array<string, \Closure(static, string, ?string, mixed):mixed> $initializer
37- * @param array<string, true> $skippedProperties An array indexed by the properties to skip, aka the ones
38- * that the initializer doesn't set when its a closure
39+ * @param (\Closure(static):void
40+ * |array<string, \Closure(static, string, ?string, mixed):mixed>
41+ * |array{"\0": \Closure(static, array<string, mixed>):array<string, mixed>}) $initializer
42+ * @param array<string, true>|null $skippedProperties An array indexed by the properties to skip, aka the ones
43+ * that the initializer doesn't set when its a closure
3944 */
40- public static function createLazyGhost (\Closure |array $ initializer , array $ skippedProperties = [] , self $ instance = null ): static
45+ public static function createLazyGhost (\Closure |array $ initializer , array $ skippedProperties = null , self $ instance = null ): static
4146 {
47+ $ onlyProperties = null === $ skippedProperties && \is_array ($ initializer ) ? $ initializer : null ;
48+
4249 if (self ::class !== $ class = $ instance ? $ instance ::class : static ::class) {
4350 $ skippedProperties ["\0" .self ::class."\0lazyObjectId " ] = true ;
4451 } elseif (\defined ($ class .'::LAZY_OBJECT_PROPERTY_SCOPES ' )) {
@@ -48,8 +55,7 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
4855 $ instance ??= (Registry::$ classReflectors [$ class ] ??= new \ReflectionClass ($ class ))->newInstanceWithoutConstructor ();
4956 Registry::$ defaultProperties [$ class ] ??= (array ) $ instance ;
5057 $ instance ->lazyObjectId = $ id = spl_object_id ($ instance );
51- Registry::$ states [$ id ] = new LazyObjectState ($ initializer , $ skippedProperties );
52- $ onlyProperties = \is_array ($ initializer ) ? $ initializer : null ;
58+ Registry::$ states [$ id ] = new LazyObjectState ($ initializer , $ skippedProperties ??= []);
5359
5460 foreach (Registry::$ classResetters [$ class ] ??= Registry::getClassResetters ($ class ) as $ reset ) {
5561 $ reset ($ instance , $ skippedProperties , $ onlyProperties );
@@ -60,8 +66,10 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
6066
6167 /**
6268 * Returns whether the object is initialized.
69+ *
70+ * @param $partial Whether partially initialized objects should be considered as initialized
6371 */
64- public function isLazyObjectInitialized (): bool
72+ public function isLazyObjectInitialized (bool $ partial = false ): bool
6573 {
6674 if (!$ state = Registry::$ states [$ this ->lazyObjectId ?? '' ] ?? null ) {
6775 return true ;
@@ -73,6 +81,11 @@ public function isLazyObjectInitialized(): bool
7381
7482 $ class = $ this ::class;
7583 $ properties = (array ) $ this ;
84+
85+ if ($ partial ) {
86+ return (bool ) array_intersect_key ($ state ->initializer , $ properties );
87+ }
88+
7689 $ propertyScopes = Hydrator::$ propertyScopes [$ class ] ??= Hydrator::getPropertyScopes ($ class );
7790 foreach ($ state ->initializer as $ key => $ initializer ) {
7891 if (!\array_key_exists ($ key , $ properties ) && isset ($ propertyScopes [$ key ])) {
@@ -100,16 +113,34 @@ public function initializeLazyObject(): static
100113 return $ this ;
101114 }
102115
116+ $ values = isset ($ state ->initializer ["\0" ]) ? null : [];
117+
103118 $ class = $ this ::class;
104119 $ properties = (array ) $ this ;
105120 $ propertyScopes = Hydrator::$ propertyScopes [$ class ] ??= Hydrator::getPropertyScopes ($ class );
106121 foreach ($ state ->initializer as $ key => $ initializer ) {
107122 if (\array_key_exists ($ key , $ properties ) || ![$ scope , $ name , $ readonlyScope ] = $ propertyScopes [$ key ] ?? null ) {
108123 continue ;
109124 }
125+ $ scope = $ readonlyScope ?? ('* ' !== $ scope ? $ scope : $ class );
110126
111- $ state ->initialize ($ this , $ name , $ readonlyScope ?? ('* ' !== $ scope ? $ scope : null ));
112- $ properties = (array ) $ this ;
127+ if (null === $ values ) {
128+ if (!\is_array ($ values = ($ state ->initializer ["\0" ])($ this , Registry::$ defaultProperties [$ class ]))) {
129+ throw new \TypeError (sprintf ('The lazy-initializer defined for instance of "%s" must return an array, got "%s". ' , $ class , get_debug_type ($ values )));
130+ }
131+
132+ if (\array_key_exists ($ key , $ properties = (array ) $ this )) {
133+ continue ;
134+ }
135+ }
136+
137+ if (\array_key_exists ($ key , $ values )) {
138+ $ accessor = Registry::$ classAccessors [$ scope ] ??= Registry::getClassAccessors ($ scope );
139+ $ accessor ['set ' ]($ this , $ name , $ properties [$ key ] = $ values [$ key ]);
140+ } else {
141+ $ state ->initialize ($ this , $ name , $ scope );
142+ $ properties = (array ) $ this ;
143+ }
113144 }
114145
115146 return $ this ;
0 commit comments