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