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