1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Core; |
4: | |
5: | use Salient\Contract\Container\ContainerInterface; |
6: | use Salient\Contract\Core\DateFormatterInterface; |
7: | use Salient\Contract\Core\Extensible; |
8: | use Salient\Contract\Core\HasName; |
9: | use Salient\Contract\Core\Normalisable; |
10: | use Salient\Contract\Core\NormaliserFactory; |
11: | use Salient\Contract\Core\NormaliserFlag; |
12: | use Salient\Contract\Core\Providable; |
13: | use Salient\Contract\Core\ProviderContextInterface; |
14: | use Salient\Contract\Core\ProviderInterface; |
15: | use Salient\Contract\Core\Relatable; |
16: | use Salient\Contract\Core\SerializeRulesInterface; |
17: | use Salient\Contract\Core\Treeable; |
18: | use Salient\Utility\Arr; |
19: | use Salient\Utility\Get; |
20: | use Closure; |
21: | use LogicException; |
22: | use UnexpectedValueException; |
23: | |
24: | |
25: | |
26: | |
27: | |
28: | |
29: | |
30: | |
31: | |
32: | |
33: | |
34: | |
35: | |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | |
45: | |
46: | |
47: | |
48: | |
49: | |
50: | |
51: | |
52: | |
53: | |
54: | |
55: | |
56: | |
57: | |
58: | |
59: | |
60: | |
61: | |
62: | |
63: | |
64: | |
65: | |
66: | class Introspector |
67: | { |
68: | |
69: | protected $_Class; |
70: | |
71: | protected $_Service; |
72: | |
73: | protected $_Provider; |
74: | |
75: | protected $_Entity; |
76: | |
77: | protected $_Context; |
78: | |
79: | private static $_IntrospectionClasses = []; |
80: | |
81: | |
82: | |
83: | |
84: | |
85: | |
86: | |
87: | |
88: | |
89: | |
90: | |
91: | |
92: | public static function getService(ContainerInterface $container, string $service) |
93: | { |
94: | return new static( |
95: | $service, |
96: | $container->getName($service), |
97: | AbstractProvider::class, |
98: | AbstractEntity::class, |
99: | ProviderContext::class, |
100: | ); |
101: | } |
102: | |
103: | |
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: | |
111: | public static function get(string $class) |
112: | { |
113: | return new static( |
114: | $class, |
115: | $class, |
116: | AbstractProvider::class, |
117: | AbstractEntity::class, |
118: | ProviderContext::class, |
119: | ); |
120: | } |
121: | |
122: | |
123: | |
124: | |
125: | |
126: | |
127: | |
128: | |
129: | |
130: | |
131: | final protected function __construct( |
132: | string $service, |
133: | string $class, |
134: | string $provider, |
135: | string $entity, |
136: | string $context |
137: | ) { |
138: | $this->_Class = |
139: | self::$_IntrospectionClasses[static::class][$class] |
140: | ?? (self::$_IntrospectionClasses[static::class][$class] = $this->getIntrospectionClass($class)); |
141: | $this->_Service = $service === $class ? null : $service; |
142: | $this->_Provider = $provider; |
143: | $this->_Entity = $entity; |
144: | $this->_Context = $context; |
145: | } |
146: | |
147: | |
148: | |
149: | |
150: | |
151: | protected function getIntrospectionClass(string $class): IntrospectionClass |
152: | { |
153: | return new IntrospectionClass($class); |
154: | } |
155: | |
156: | |
157: | |
158: | |
159: | |
160: | final public function __call(string $name, array $arguments) |
161: | { |
162: | return $this->_Class->{$name}(...$arguments); |
163: | } |
164: | |
165: | |
166: | |
167: | |
168: | final public function __get(string $name) |
169: | { |
170: | return $this->_Class->{$name}; |
171: | } |
172: | |
173: | |
174: | |
175: | |
176: | |
177: | |
178: | |
179: | |
180: | |
181: | |
182: | |
183: | |
184: | |
185: | |
186: | final public function maybeNormalise($value, int $flags = NormaliserFlag::GREEDY) |
187: | { |
188: | return $this->_Class->maybeNormalise($value, $flags); |
189: | } |
190: | |
191: | |
192: | |
193: | |
194: | final public function hasNormaliser(): bool |
195: | { |
196: | return $this->_Class->Normaliser !== null; |
197: | } |
198: | |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | |
205: | |
206: | |
207: | |
208: | |
209: | final public function getCreateFromClosure(bool $strict = false): Closure |
210: | { |
211: | $closure = |
212: | $this->_Class->CreateProviderlessFromClosures[(int) $strict] |
213: | ?? null; |
214: | |
215: | if ($closure) { |
216: | return $closure; |
217: | } |
218: | |
219: | $closure = |
220: | function ( |
221: | array $array, |
222: | ContainerInterface $container, |
223: | ?DateFormatterInterface $dateFormatter = null, |
224: | ?Treeable $parent = null |
225: | ) use ($strict) { |
226: | $keys = array_keys($array); |
227: | $closure = $this->getCreateFromSignatureClosure($keys, $strict); |
228: | return $closure($array, $container, $dateFormatter, $parent); |
229: | }; |
230: | |
231: | $this->_Class->CreateProviderlessFromClosures[(int) $strict] = $closure; |
232: | |
233: | return $closure; |
234: | } |
235: | |
236: | |
237: | |
238: | |
239: | |
240: | |
241: | |
242: | |
243: | |
244: | |
245: | final public function getCreateFromSignatureClosure(array $keys, bool $strict = false): Closure |
246: | { |
247: | $sig = implode("\0", $keys); |
248: | |
249: | $closure = |
250: | $this->_Class->CreateProviderlessFromSignatureClosures[$sig][(int) $strict] |
251: | ?? null; |
252: | |
253: | if (!$closure) { |
254: | $closure = $this->_getCreateFromSignatureClosure($keys, $strict); |
255: | $this->_Class->CreateProviderlessFromSignatureClosures[$sig][(int) $strict] = $closure; |
256: | |
257: | |
258: | |
259: | if ($strict) { |
260: | $this->_Class->CreateProviderlessFromSignatureClosures[$sig][(int) false] = $closure; |
261: | } |
262: | } |
263: | |
264: | |
265: | $service = $this->_Service; |
266: | |
267: | return |
268: | static function ( |
269: | array $array, |
270: | ContainerInterface $container, |
271: | ?DateFormatterInterface $dateFormatter = null, |
272: | ?Treeable $parent = null |
273: | ) use ($closure, $service) { |
274: | return $closure( |
275: | $array, |
276: | $service, |
277: | $container, |
278: | null, |
279: | null, |
280: | $dateFormatter, |
281: | $parent, |
282: | ); |
283: | }; |
284: | } |
285: | |
286: | |
287: | |
288: | |
289: | |
290: | |
291: | |
292: | |
293: | |
294: | |
295: | |
296: | |
297: | final public function getCreateProvidableFromClosure(bool $strict = false): Closure |
298: | { |
299: | $closure = |
300: | $this->_Class->CreateProvidableFromClosures[(int) $strict] |
301: | ?? null; |
302: | |
303: | if ($closure) { |
304: | return $closure; |
305: | } |
306: | |
307: | $closure = |
308: | function ( |
309: | array $array, |
310: | ProviderInterface $provider, |
311: | ProviderContextInterface $context |
312: | ) use ($strict) { |
313: | $keys = array_keys($array); |
314: | $closure = $this->getCreateProvidableFromSignatureClosure($keys, $strict); |
315: | return $closure($array, $provider, $context); |
316: | }; |
317: | |
318: | return $this->_Class->CreateProvidableFromClosures[(int) $strict] = $closure; |
319: | } |
320: | |
321: | |
322: | |
323: | |
324: | |
325: | |
326: | |
327: | |
328: | |
329: | |
330: | final public function getCreateProvidableFromSignatureClosure(array $keys, bool $strict = false): Closure |
331: | { |
332: | $sig = implode("\0", $keys); |
333: | |
334: | $closure = |
335: | $this->_Class->CreateProvidableFromSignatureClosures[$sig][(int) $strict] |
336: | ?? null; |
337: | |
338: | if (!$closure) { |
339: | $closure = $this->_getCreateFromSignatureClosure($keys, $strict); |
340: | $this->_Class->CreateProvidableFromSignatureClosures[$sig][(int) $strict] = $closure; |
341: | |
342: | |
343: | |
344: | if ($strict) { |
345: | $this->_Class->CreateProvidableFromSignatureClosures[$sig][(int) false] = $closure; |
346: | } |
347: | } |
348: | |
349: | |
350: | $service = $this->_Service; |
351: | |
352: | return |
353: | static function ( |
354: | array $array, |
355: | ProviderInterface $provider, |
356: | ProviderContextInterface $context |
357: | ) use ($closure, $service) { |
358: | return $closure( |
359: | $array, |
360: | $service, |
361: | $context->getContainer(), |
362: | $provider, |
363: | $context, |
364: | $provider->getDateFormatter(), |
365: | $context->getParent(), |
366: | ); |
367: | }; |
368: | } |
369: | |
370: | |
371: | |
372: | |
373: | |
374: | private function _getCreateFromSignatureClosure(array $keys, bool $strict = false): Closure |
375: | { |
376: | $sig = implode("\0", $keys); |
377: | if ($closure = $this->_Class->CreateFromSignatureClosures[$sig] ?? null) { |
378: | return $closure; |
379: | } |
380: | |
381: | $targets = $this->getKeyTargets($keys, true, $strict); |
382: | $constructor = $this->_getConstructor($targets); |
383: | $updater = $this->_getUpdater($targets); |
384: | $resolver = $this->_getResolver($targets); |
385: | |
386: | $closure = static function ( |
387: | array $array, |
388: | ?string $service, |
389: | ContainerInterface $container, |
390: | ?ProviderInterface $provider, |
391: | ?ProviderContextInterface $context, |
392: | ?DateFormatterInterface $dateFormatter, |
393: | ?Treeable $parent |
394: | ) use ($constructor, $updater, $resolver) { |
395: | $obj = $constructor($array, $service, $container); |
396: | $obj = $updater($array, $obj, $container, $provider, $context, $dateFormatter, $parent); |
397: | $obj = $resolver($array, $service, $obj, $provider, $context); |
398: | if ($obj instanceof Providable) { |
399: | $obj->postLoad(); |
400: | } |
401: | return $obj; |
402: | }; |
403: | |
404: | return $this->_Class->CreateFromSignatureClosures[$sig] = $closure; |
405: | } |
406: | |
407: | |
408: | |
409: | |
410: | |
411: | |
412: | |
413: | |
414: | |
415: | |
416: | |
417: | |
418: | |
419: | |
420: | |
421: | |
422: | |
423: | protected function getKeyTargets( |
424: | array $keys, |
425: | bool $forNewInstance, |
426: | bool $strict, |
427: | bool $normalised = false, |
428: | array $customKeys = [], |
429: | array $keyClosures = [] |
430: | ): IntrospectorKeyTargets { |
431: | if (!$normalised) { |
432: | $keys = |
433: | $this->_Class->Normaliser |
434: | ? array_combine(array_map($this->_Class->CarefulNormaliser, $keys), $keys) |
435: | : array_combine($keys, $keys); |
436: | } |
437: | |
438: | |
439: | |
440: | |
441: | |
442: | $keys = array_diff_key($keys, $keyClosures); |
443: | |
444: | |
445: | |
446: | if ($forNewInstance) { |
447: | $missing = array_diff_key( |
448: | $this->_Class->RequiredParameters, |
449: | $this->_Class->ServiceParameters, |
450: | $keys, |
451: | ); |
452: | if ($missing) { |
453: | throw new LogicException(sprintf( |
454: | 'Cannot call %s::__construct() without: %s', |
455: | $this->_Class->Class, |
456: | implode(', ', $missing), |
457: | )); |
458: | } |
459: | } else { |
460: | |
461: | |
462: | $parameters = array_intersect_key( |
463: | $this->_Class->Parameters, |
464: | $keys, |
465: | ); |
466: | $readonly = array_diff_key( |
467: | $parameters, |
468: | array_flip($this->_Class->getWritableProperties()), |
469: | ); |
470: | if ($readonly) { |
471: | throw new LogicException(sprintf( |
472: | 'Cannot set readonly properties of %s: %s', |
473: | $this->_Class->Class, |
474: | implode(', ', $readonly), |
475: | )); |
476: | } |
477: | } |
478: | |
479: | |
480: | $dateKeys = array_values(array_intersect_key( |
481: | $keys, |
482: | array_flip($this->_Class->DateKeys) + $this->_Class->DateParameters, |
483: | )); |
484: | |
485: | $keys += $keyClosures; |
486: | |
487: | |
488: | |
489: | |
490: | |
491: | |
492: | |
493: | |
494: | |
495: | foreach ($keys as $normalisedKey => $key) { |
496: | if ($key instanceof Closure) { |
497: | $callbackKeys[] = $key; |
498: | continue; |
499: | } |
500: | |
501: | if ($forNewInstance) { |
502: | $param = $this->_Class->Parameters[$normalisedKey] ?? null; |
503: | if ($param !== null) { |
504: | $parameterKeys[$key] = $this->_Class->ParameterIndex[$param]; |
505: | if (isset($this->_Class->PassByRefParameters[$normalisedKey])) { |
506: | $passByRefKeys[$key] = true; |
507: | } |
508: | if (isset($this->_Class->NotNullableParameters[$normalisedKey])) { |
509: | $notNullableKeys[$key] = true; |
510: | } |
511: | continue; |
512: | } |
513: | } |
514: | |
515: | $method = $this->_Class->Actions[IntrospectionClass::ACTION_SET][$normalisedKey] ?? null; |
516: | if ($method !== null) { |
517: | $methodKeys[$key] = $method; |
518: | continue; |
519: | } |
520: | |
521: | $property = $this->_Class->Properties[$normalisedKey] ?? null; |
522: | if ($property !== null) { |
523: | |
524: | if ($this->_Class->propertyActionIsAllowed( |
525: | $normalisedKey, IntrospectionClass::ACTION_SET |
526: | )) { |
527: | $propertyKeys[$key] = $property; |
528: | continue; |
529: | } |
530: | if ($strict) { |
531: | throw new LogicException(sprintf( |
532: | 'Cannot set unwritable property: %s::$%s', |
533: | $this->_Class->Class, |
534: | $property, |
535: | )); |
536: | } |
537: | continue; |
538: | } |
539: | |
540: | if ($this->_Class->IsExtensible) { |
541: | $metaKeys[] = $key; |
542: | continue; |
543: | } |
544: | |
545: | if ($strict) { |
546: | throw new LogicException(sprintf( |
547: | 'Cannot apply %s to %s', |
548: | $key, |
549: | $this->_Class->Class, |
550: | )); |
551: | } |
552: | } |
553: | |
554: | |
555: | $targets = new IntrospectorKeyTargets( |
556: | $parameterKeys ?? [], |
557: | $passByRefKeys ?? [], |
558: | $notNullableKeys ?? [], |
559: | $callbackKeys ?? [], |
560: | $methodKeys ?? [], |
561: | $propertyKeys ?? [], |
562: | $metaKeys ?? [], |
563: | $dateKeys, |
564: | $customKeys, |
565: | ); |
566: | |
567: | return $targets; |
568: | } |
569: | |
570: | |
571: | |
572: | |
573: | |
574: | final protected function _getConstructor(IntrospectorKeyTargets $targets): Closure |
575: | { |
576: | $length = max( |
577: | $this->_Class->RequiredArguments, |
578: | $targets->LastParameterIndex + 1, |
579: | ); |
580: | |
581: | $args = array_slice($this->_Class->DefaultArguments, 0, $length); |
582: | $class = $this->_Class->Class; |
583: | |
584: | if (!$targets->Parameters) { |
585: | return static function ( |
586: | array $array, |
587: | ?string $service, |
588: | ContainerInterface $container |
589: | ) use ($args, $class) { |
590: | if ($service && strcasecmp($service, $class)) { |
591: | |
592: | return $container->getAs($class, $service, $args); |
593: | } |
594: | return $container->get($class, $args); |
595: | }; |
596: | } |
597: | |
598: | |
599: | $serviceArgs = array_intersect_key( |
600: | $this->_Class->ParameterIndex, |
601: | array_flip(array_intersect_key( |
602: | $this->_Class->Parameters, |
603: | $this->_Class->ServiceParameters, |
604: | )), |
605: | ); |
606: | |
607: | |
608: | $serviceArgs = array_intersect($serviceArgs, array_keys($args)); |
609: | |
610: | |
611: | |
612: | |
613: | $missingServiceArgs = array_diff($serviceArgs, $targets->Parameters); |
614: | $args = array_diff_key($args, array_flip($missingServiceArgs)); |
615: | |
616: | $serviceArgs = array_fill_keys(array_intersect( |
617: | $serviceArgs, |
618: | $targets->Parameters, |
619: | ), true); |
620: | |
621: | $parameterKeys = $targets->Parameters; |
622: | $passByRefKeys = $targets->PassByRefParameters; |
623: | $notNullableKeys = $targets->NotNullableParameters; |
624: | |
625: | return static function ( |
626: | array $array, |
627: | ?string $service, |
628: | ContainerInterface $container |
629: | ) use ( |
630: | $args, |
631: | $class, |
632: | $serviceArgs, |
633: | $parameterKeys, |
634: | $passByRefKeys, |
635: | $notNullableKeys |
636: | ) { |
637: | foreach ($parameterKeys as $key => $index) { |
638: | if ($array[$key] === null) { |
639: | if ($serviceArgs[$index] ?? false) { |
640: | unset($args[$index]); |
641: | continue; |
642: | } |
643: | if ($notNullableKeys[$key] ?? false) { |
644: | throw new LogicException(sprintf( |
645: | "Argument #%d is not nullable, cannot apply value at key '%s': %s::__construct()", |
646: | $index + 1, |
647: | $key, |
648: | $class, |
649: | )); |
650: | } |
651: | } |
652: | if ($passByRefKeys[$key] ?? false) { |
653: | $args[$index] = &$array[$key]; |
654: | continue; |
655: | } |
656: | $args[$index] = $array[$key]; |
657: | } |
658: | |
659: | if ($service && strcasecmp($service, $class)) { |
660: | |
661: | return $container->getAs($class, $service, $args); |
662: | } |
663: | |
664: | return $container->get($class, $args); |
665: | }; |
666: | } |
667: | |
668: | |
669: | |
670: | |
671: | |
672: | |
673: | |
674: | |
675: | |
676: | |
677: | |
678: | |
679: | |
680: | |
681: | |
682: | |
683: | |
684: | |
685: | |
686: | |
687: | |
688: | |
689: | |
690: | |
691: | final public function getPropertyActionClosure(string $name, string $action): Closure |
692: | { |
693: | $_name = $this->_Class->maybeNormalise($name, NormaliserFlag::CAREFUL); |
694: | |
695: | if ($closure = $this->_Class->PropertyActionClosures[$_name][$action] ?? null) { |
696: | return $closure; |
697: | } |
698: | |
699: | if (!in_array($action, [ |
700: | IntrospectionClass::ACTION_SET, |
701: | IntrospectionClass::ACTION_GET, |
702: | IntrospectionClass::ACTION_ISSET, |
703: | IntrospectionClass::ACTION_UNSET |
704: | ])) { |
705: | throw new UnexpectedValueException("Invalid action: $action"); |
706: | } |
707: | |
708: | if ($method = $this->_Class->Actions[$action][$_name] ?? null) { |
709: | $closure = static function ($instance, ...$params) use ($method) { |
710: | return $instance->$method(...$params); |
711: | }; |
712: | } elseif ($property = $this->_Class->Properties[$_name] ?? null) { |
713: | if ($this->_Class->propertyActionIsAllowed($_name, $action)) { |
714: | switch ($action) { |
715: | case IntrospectionClass::ACTION_SET: |
716: | $closure = static function ($instance, $value) use ($property) { $instance->$property = $value; }; |
717: | break; |
718: | |
719: | case IntrospectionClass::ACTION_GET: |
720: | $closure = static function ($instance) use ($property) { return $instance->$property; }; |
721: | break; |
722: | |
723: | case IntrospectionClass::ACTION_ISSET: |
724: | $closure = static function ($instance) use ($property) { return isset($instance->$property); }; |
725: | break; |
726: | |
727: | case IntrospectionClass::ACTION_UNSET: |
728: | |
729: | |
730: | $closure = static function ($instance) use ($property) { $instance->$property = null; }; |
731: | break; |
732: | } |
733: | } |
734: | } elseif ($this->_Class->IsExtensible) { |
735: | $method = $action == IntrospectionClass::ACTION_ISSET ? 'isMetaPropertySet' : $action . 'MetaProperty'; |
736: | $closure = static function ($instance, ...$params) use ($method, $name) { |
737: | return $instance->$method($name, ...$params); |
738: | }; |
739: | } |
740: | |
741: | if (!$closure) { |
742: | throw new UnexpectedValueException("Unable to perform '$action' on property '$name'"); |
743: | } |
744: | |
745: | $closure = $closure->bindTo(null, $this->_Class->Class); |
746: | |
747: | return $this->_Class->PropertyActionClosures[$_name][$action] = $closure; |
748: | } |
749: | |
750: | |
751: | |
752: | |
753: | |
754: | |
755: | |
756: | |
757: | |
758: | final public function getGetNameClosure(): Closure |
759: | { |
760: | if ($this->_Class->GetNameClosure) { |
761: | return $this->_Class->GetNameClosure; |
762: | } |
763: | |
764: | $names = [ |
765: | 'display_name', |
766: | 'displayname', |
767: | 'name', |
768: | 'full_name', |
769: | 'fullname', |
770: | 'surname', |
771: | 'last_name', |
772: | 'first_name', |
773: | 'title', |
774: | 'id', |
775: | ]; |
776: | |
777: | $names = array_combine( |
778: | $names, |
779: | $this->_Class->maybeNormalise($names, NormaliserFlag::CAREFUL) |
780: | ); |
781: | |
782: | $surname = $names['surname']; |
783: | $lastName = $names['last_name']; |
784: | $firstName = $names['first_name']; |
785: | $id = $names['id']; |
786: | |
787: | $names = array_intersect( |
788: | $names, |
789: | $this->_Class->getReadableProperties() |
790: | ); |
791: | |
792: | |
793: | |
794: | $maybeLast = reset($names); |
795: | if (in_array($maybeLast, [$surname, $lastName], true)) { |
796: | array_shift($names); |
797: | $maybeFirst = reset($names); |
798: | if ($maybeFirst === $firstName) { |
799: | $last = $this->getPropertyActionClosure( |
800: | $maybeLast, |
801: | IntrospectionClass::ACTION_GET |
802: | ); |
803: | $first = $this->getPropertyActionClosure( |
804: | $maybeFirst, |
805: | IntrospectionClass::ACTION_GET |
806: | ); |
807: | |
808: | return $this->_Class->GetNameClosure = |
809: | static function ( |
810: | $instance |
811: | ) use ($first, $last): string { |
812: | return Arr::implode(' ', [ |
813: | $first($instance), |
814: | $last($instance), |
815: | ], ''); |
816: | }; |
817: | } |
818: | } |
819: | unset($names['last_name']); |
820: | unset($names['first_name']); |
821: | |
822: | if (!$names) { |
823: | $name = Get::basename($this->_Class->Class); |
824: | $name = "<$name>"; |
825: | return $this->_Class->GetNameClosure = |
826: | static function () use ($name): string { |
827: | return $name; |
828: | }; |
829: | } |
830: | |
831: | $name = array_shift($names); |
832: | $closure = $this->getPropertyActionClosure( |
833: | $name, |
834: | IntrospectionClass::ACTION_GET |
835: | ); |
836: | |
837: | return $this->_Class->GetNameClosure = |
838: | $name === $id |
839: | ? static function ($instance) use ($closure): string { |
840: | return '#' . $closure($instance); |
841: | } |
842: | : static function ($instance) use ($closure): string { |
843: | return (string) $closure($instance); |
844: | }; |
845: | } |
846: | |
847: | |
848: | |
849: | |
850: | final public function getSerializeClosure(?SerializeRulesInterface $rules = null): Closure |
851: | { |
852: | $rules = $rules |
853: | ? [$rules->getSortByKey(), $this->_Class->IsExtensible && $rules->getIncludeMeta()] |
854: | : [false, $this->_Class->IsExtensible]; |
855: | $key = implode("\0", $rules); |
856: | |
857: | if ($closure = $this->_Class->SerializeClosures[$key] ?? null) { |
858: | return $closure; |
859: | } |
860: | |
861: | [$sort, $includeMeta] = $rules; |
862: | $methods = $this->_Class->Actions[IntrospectionClass::ACTION_GET] ?? []; |
863: | $props = array_intersect( |
864: | $this->_Class->Properties, |
865: | $this->_Class->ReadableProperties ?: $this->_Class->PublicProperties |
866: | ); |
867: | $keys = array_keys($props + $methods); |
868: | if ($sort) { |
869: | sort($keys); |
870: | } |
871: | |
872: | |
873: | $resolveIterator = function (&$value): void { |
874: | if (is_iterable($value) && !is_array($value)) { |
875: | $value = iterator_to_array($value); |
876: | } |
877: | }; |
878: | $closure = (static function ($instance) use ($keys, $methods, $props, $resolveIterator) { |
879: | $arr = []; |
880: | foreach ($keys as $key) { |
881: | if ($method = $methods[$key] ?? null) { |
882: | $arr[$key] = $instance->{$method}(); |
883: | $resolveIterator($arr[$key]); |
884: | } else { |
885: | $resolveIterator($instance->{$props[$key]}); |
886: | $arr[$key] = $instance->{$props[$key]}; |
887: | } |
888: | } |
889: | |
890: | return $arr; |
891: | })->bindTo(null, $this->_Class->Class); |
892: | |
893: | if ($includeMeta) { |
894: | $closure = static function (Extensible $instance) use ($closure) { |
895: | $meta = $instance->getMetaProperties(); |
896: | |
897: | return ($meta ? ['@meta' => $meta] : []) + $closure($instance); |
898: | }; |
899: | } |
900: | |
901: | return $this->_Class->SerializeClosures[$key] = $closure; |
902: | } |
903: | |
904: | |
905: | |
906: | |
907: | |
908: | final protected function _getUpdater(IntrospectorKeyTargets $targets): Closure |
909: | { |
910: | $isProvidable = $this->_Class->IsProvidable; |
911: | $isTreeable = $this->_Class->IsTreeable; |
912: | $methodKeys = $targets->Methods; |
913: | $propertyKeys = $targets->Properties; |
914: | $metaKeys = $targets->MetaProperties; |
915: | $dateKeys = $targets->DateProperties; |
916: | |
917: | $closure = static function ( |
918: | array $array, |
919: | $obj, |
920: | ContainerInterface $container, |
921: | ?ProviderInterface $provider, |
922: | ?ProviderContextInterface $context, |
923: | ?DateFormatterInterface $dateFormatter, |
924: | ?Treeable $parent |
925: | ) use ( |
926: | $isProvidable, |
927: | $isTreeable, |
928: | $methodKeys, |
929: | $propertyKeys, |
930: | $metaKeys, |
931: | $dateKeys |
932: | ) { |
933: | if ($dateKeys) { |
934: | if ($dateFormatter === null) { |
935: | $dateFormatter = |
936: | $provider |
937: | ? $provider->getDateFormatter() |
938: | : $container->get(DateFormatter::class); |
939: | } |
940: | |
941: | foreach ($dateKeys as $key) { |
942: | if (!is_string($array[$key])) { |
943: | continue; |
944: | } |
945: | if ($date = $dateFormatter->parse($array[$key])) { |
946: | $array[$key] = $date; |
947: | } |
948: | } |
949: | } |
950: | |
951: | |
952: | |
953: | if ($propertyKeys) { |
954: | foreach ($propertyKeys as $key => $property) { |
955: | $obj->$property = $array[$key]; |
956: | } |
957: | } |
958: | |
959: | |
960: | |
961: | if ($isProvidable && $provider) { |
962: | if (!$context) { |
963: | throw new UnexpectedValueException('$context cannot be null when $provider is not null'); |
964: | } |
965: | |
966: | $currentProvider = $obj->getProvider(); |
967: | if ($currentProvider === null) { |
968: | $obj = $obj->setProvider($provider); |
969: | } elseif ($currentProvider !== $provider) { |
970: | throw new LogicException(sprintf( |
971: | '%s has wrong provider (%s expected): %s', |
972: | get_class($obj), |
973: | $provider->getName(), |
974: | $currentProvider->getName(), |
975: | )); |
976: | } |
977: | $obj = $obj->setContext($context); |
978: | } |
979: | |
980: | |
981: | if ($isTreeable && $parent) { |
982: | |
983: | $obj = $obj->setParent($parent); |
984: | } |
985: | |
986: | |
987: | if ($methodKeys) { |
988: | foreach ($methodKeys as $key => $method) { |
989: | $obj->$method($array[$key]); |
990: | } |
991: | } |
992: | |
993: | if ($metaKeys) { |
994: | foreach ($metaKeys as $key) { |
995: | $obj->setMetaProperty((string) $key, $array[$key]); |
996: | } |
997: | } |
998: | |
999: | return $obj; |
1000: | }; |
1001: | |
1002: | return $closure->bindTo(null, $this->_Class->Class); |
1003: | } |
1004: | |
1005: | |
1006: | |
1007: | |
1008: | |
1009: | final protected function _getResolver(IntrospectorKeyTargets $targets): Closure |
1010: | { |
1011: | $callbackKeys = $targets->Callbacks; |
1012: | |
1013: | $closure = static function ( |
1014: | array $array, |
1015: | ?string $service, |
1016: | $obj, |
1017: | ?ProviderInterface $provider, |
1018: | ?ProviderContextInterface $context |
1019: | ) use ($callbackKeys) { |
1020: | if ($callbackKeys) { |
1021: | foreach ($callbackKeys as $callback) { |
1022: | $callback($array, $service, $obj, $provider, $context); |
1023: | } |
1024: | } |
1025: | |
1026: | return $obj; |
1027: | }; |
1028: | |
1029: | return $closure->bindTo(null, $this->_Class->Class); |
1030: | } |
1031: | } |
1032: | |