1: <?php declare(strict_types=1);
2:
3: namespace Salient\Core;
4:
5: use Salient\Container\RequiresContainer;
6: use Salient\Contract\Container\ContainerInterface;
7: use Salient\Contract\Core\BuilderInterface;
8: use Salient\Core\Concern\HasChainableMethods;
9: use Salient\Utility\Exception\InvalidArgumentTypeException;
10: use InvalidArgumentException;
11:
12: /**
13: * Base class for builders
14: *
15: * @api
16: *
17: * @template TClass of object
18: *
19: * @implements BuilderInterface<TClass>
20: */
21: abstract class AbstractBuilder implements BuilderInterface
22: {
23: use HasChainableMethods;
24: use RequiresContainer;
25:
26: /**
27: * Get the class to build
28: *
29: * @return class-string<TClass>
30: */
31: abstract protected static function getService(): string;
32:
33: /**
34: * Get methods to forward to a new instance of the service class
35: *
36: * @return string[]
37: */
38: protected static function getTerminators(): array
39: {
40: return [];
41: }
42:
43: /** @todo Decouple from the service container */
44: protected ContainerInterface $Container;
45: /** @var Introspector<object,AbstractProvider,AbstractEntity,ProviderContext<AbstractProvider,AbstractEntity>> */
46: private Introspector $Introspector;
47: /** @var array<string,true> */
48: private array $Terminators = [];
49: /** @var array<string,mixed> */
50: private array $Data = [];
51:
52: /**
53: * Creates a new builder
54: */
55: final public function __construct(?ContainerInterface $container = null)
56: {
57: $this->Container = self::requireContainer($container);
58: $this->Introspector = Introspector::getService($this->Container, static::getService());
59: foreach (static::getTerminators() as $terminator) {
60: $this->Terminators[$terminator] = true;
61: $this->Terminators[$this->Introspector->maybeNormalise($terminator)] = true;
62: }
63: }
64:
65: /**
66: * @inheritDoc
67: */
68: final public static function create(?ContainerInterface $container = null)
69: {
70: return new static($container);
71: }
72:
73: /**
74: * @inheritDoc
75: */
76: final public static function resolve($object)
77: {
78: if ($object instanceof static) {
79: return $object->build();
80: }
81:
82: if (is_a($object, static::getService())) {
83: return $object;
84: }
85:
86: throw new InvalidArgumentTypeException(
87: 1,
88: 'object',
89: static::class . '|' . static::getService(),
90: $object,
91: );
92: }
93:
94: /**
95: * Get the builder's container
96: */
97: final public function getContainer(): ContainerInterface
98: {
99: return $this->Container;
100: }
101:
102: /**
103: * @inheritDoc
104: */
105: final public function getB(string $name)
106: {
107: return $this->Data[$this->Introspector->maybeNormalise($name)] ?? null;
108: }
109:
110: /**
111: * @inheritDoc
112: */
113: final public function issetB(string $name): bool
114: {
115: return array_key_exists($this->Introspector->maybeNormalise($name), $this->Data);
116: }
117:
118: /**
119: * @inheritDoc
120: */
121: final public function unsetB(string $name)
122: {
123: $name = $this->Introspector->maybeNormalise($name);
124: if (!array_key_exists($name, $this->Data)) {
125: return $this;
126: }
127: $clone = clone $this;
128: unset($clone->Data[$name]);
129: return $clone;
130: }
131:
132: /**
133: * @inheritDoc
134: */
135: final public function build()
136: {
137: /** @var TClass */
138: return $this->Introspector->getCreateFromClosure(true)($this->Data, $this->Container);
139: }
140:
141: /**
142: * @internal
143: *
144: * @param mixed[] $arguments
145: * @return static
146: */
147: final public function __call(string $name, array $arguments)
148: {
149: if (
150: ($this->Terminators[$name] ?? null)
151: || ($this->Terminators[$this->Introspector->maybeNormalise($name)] ?? null)
152: ) {
153: return $this->build()->{$name}(...$arguments);
154: }
155:
156: $count = count($arguments);
157: if ($count > 1) {
158: throw new InvalidArgumentException('Too many arguments');
159: }
160:
161: return $this->withValueB($name, $count ? $arguments[0] : true);
162: }
163:
164: /**
165: * @param mixed $value
166: * @return static
167: */
168: final protected function withValueB(string $name, $value)
169: {
170: $name = $this->Introspector->maybeNormalise($name);
171: if (array_key_exists($name, $this->Data) && $this->Data[$name] === $value) {
172: return $this;
173: }
174: $clone = clone $this;
175: $clone->Data[$name] = $value;
176: return $clone;
177: }
178:
179: /**
180: * @param mixed $variable
181: * @return static
182: */
183: final protected function withRefB(string $name, &$variable)
184: {
185: $name = $this->Introspector->maybeNormalise($name);
186: $clone = clone $this;
187: $clone->Data[$name] = &$variable;
188: return $clone;
189: }
190:
191: /**
192: * @deprecated Use {@see build()} instead
193: *
194: * @return TClass
195: *
196: * @codeCoverageIgnore
197: */
198: final public function go()
199: {
200: return $this->build();
201: }
202: }
203: