1: <?php declare(strict_types=1);
2:
3: namespace Salient\Core\Concern;
4:
5: use Salient\Contract\Core\Entity\Extensible;
6: use Salient\Contract\Core\Provider\Providable;
7: use Salient\Contract\Core\Provider\ProviderContextInterface;
8: use Salient\Contract\Core\Provider\ProviderInterface;
9: use Salient\Contract\Core\ListConformity;
10: use Salient\Contract\Iterator\FluentIteratorInterface;
11: use Salient\Core\Introspector;
12: use Salient\Iterator\IterableIterator;
13: use Generator;
14: use LogicException;
15:
16: /**
17: * Implements Providable to represent an external entity
18: *
19: * @template TProvider of ProviderInterface
20: * @template TContext of ProviderContextInterface
21: *
22: * @see Providable
23: */
24: trait ProvidableTrait
25: {
26: /** @var TProvider|null */
27: private $Provider;
28: /** @var TContext|null */
29: private $Context;
30: /** @var class-string|null */
31: private $Service;
32:
33: /**
34: * @param TProvider $provider
35: * @return $this
36: */
37: final public function setProvider(ProviderInterface $provider)
38: {
39: if ($this->Provider) {
40: throw new LogicException('Provider already set');
41: }
42: $this->Provider = $provider;
43:
44: return $this;
45: }
46:
47: /**
48: * @return TProvider|null
49: */
50: final public function getProvider(): ?ProviderInterface
51: {
52: return $this->Provider;
53: }
54:
55: /**
56: * @param TContext $context
57: * @return $this
58: */
59: final public function setContext(ProviderContextInterface $context)
60: {
61: $this->Context = $context;
62:
63: return $this;
64: }
65:
66: /**
67: * @return TContext|null
68: */
69: final public function getContext(): ?ProviderContextInterface
70: {
71: return $this->Context;
72: }
73:
74: /**
75: * @param class-string $service
76: */
77: final public function setService(string $service): void
78: {
79: $this->Service = $service;
80: }
81:
82: /**
83: * @return class-string
84: */
85: final public function getService(): string
86: {
87: return $this->Service ?? static::class;
88: }
89:
90: /**
91: * Create an instance of the class from an array on behalf of a provider
92: *
93: * The constructor (if any) is invoked with values from `$data`. If `$data`
94: * values remain, they are assigned to writable properties. If further
95: * values remain and the class implements {@see Extensible}, they are
96: * assigned to dynamic properties.
97: *
98: * `$data` keys, constructor parameters and writable properties are
99: * normalised for comparison.
100: *
101: * @param mixed[] $data
102: * @param TProvider $provider
103: * @param TContext|null $context
104: * @return static
105: */
106: final public static function provide(
107: array $data,
108: ProviderInterface $provider,
109: ?ProviderContextInterface $context = null
110: ) {
111: $container = $context
112: ? $context->getContainer()
113: : $provider->getContainer();
114: $container = $container->inContextOf(get_class($provider));
115:
116: $context = ($context ?? $provider->getContext())->withContainer($container);
117:
118: $closure = Introspector::getService(
119: $container, static::class
120: )->getCreateProvidableFromClosure();
121:
122: return $closure($data, $provider, $context);
123: }
124:
125: /**
126: * Create instances of the class from arrays on behalf of a provider
127: *
128: * See {@see ProvidableTrait::provide()} for more information.
129: *
130: * @param iterable<array-key,mixed[]> $list
131: * @param TProvider $provider
132: * @param ListConformity::* $conformity
133: * @param TContext|null $context
134: * @return FluentIteratorInterface<array-key,static>
135: */
136: final public static function provideMultiple(
137: iterable $list,
138: ProviderInterface $provider,
139: int $conformity = ListConformity::NONE,
140: ?ProviderContextInterface $context = null
141: ): FluentIteratorInterface {
142: return IterableIterator::from(
143: self::_provideMultiple($list, $provider, $conformity, $context)
144: );
145: }
146:
147: /**
148: * @param iterable<array-key,mixed[]> $list
149: * @param TProvider $provider
150: * @param ListConformity::* $conformity
151: * @param TContext|null $context
152: * @return Generator<array-key,static>
153: */
154: private static function _provideMultiple(
155: iterable $list,
156: ProviderInterface $provider,
157: $conformity,
158: ?ProviderContextInterface $context
159: ): Generator {
160: $container = $context
161: ? $context->getContainer()
162: : $provider->getContainer();
163: $container = $container->inContextOf(get_class($provider));
164:
165: $conformity = $context
166: ? max($context->getConformity(), $conformity)
167: : $conformity;
168:
169: $context = ($context ?? $provider->getContext())->withContainer($container);
170:
171: $introspector = Introspector::getService($container, static::class);
172:
173: foreach ($list as $key => $data) {
174: if (!isset($closure)) {
175: $closure = $conformity === ListConformity::PARTIAL || $conformity === ListConformity::COMPLETE
176: ? $introspector->getCreateProvidableFromSignatureClosure(array_keys($data))
177: : $introspector->getCreateProvidableFromClosure();
178: }
179:
180: yield $key => $closure($data, $provider, $context);
181: }
182: }
183: }
184: