AuthenticatorsListFactory::withEntryPoint()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * This file is part of web-stack
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Slick\WebStack\Infrastructure\Http\Authenticator\Factory;
13
14
use ArrayIterator;
15
use Slick\WebStack\Infrastructure\Http\Authenticator\AccessTokenAuthenticator;
16
use Slick\WebStack\Infrastructure\Http\Authenticator\HttpBasicAuthenticator;
17
use ArrayAccess;
18
use IteratorAggregate;
19
use Slick\Di\ContainerInterface;
20
use Slick\WebStack\Domain\Security\Http\AuthenticationEntryPointInterface;
21
use Slick\WebStack\Domain\Security\Http\AuthenticatorInterface;
22
use Slick\WebStack\Domain\Security\Http\SecurityProfile\EntryPointAwareInterface;
23
use Slick\WebStack\Domain\Security\UserInterface;
24
use Traversable;
25
26
/**
27
 * AuthenticatorsListFactory
28
 *
29
 * @package Slick\WebStack\Domain\Security\Http\SecurityProfile
30
 * @template TUser of UserInterface
31
 * @template T of AuthenticatorInterface
32
 *
33
 * @implements ArrayAccess<int|string, T<TUser>>
34
 * @implements IteratorAggregate<int|string, T<TUser>>
35
 */
36
final class AuthenticatorsListFactory implements ArrayAccess, IteratorAggregate, EntryPointAwareInterface
37
{
38
    /**
39
     * @var array<int|string|null, T<TUser>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<int|string|null, T<TUser>> at position 8 could not be parsed: Expected '>' at position 8, but found 'T'.
Loading history...
40
     */
41
    private array $authenticators = [];
42
43
    /** @var array<string, mixed|array<string, mixed>>  */
44
    private static array $presets = [
45
        'custom' => [
46
            'className' => null,
47
            'args' => []
48
        ],
49
        'accessToken' => [
50
            'factoryClass' => AccessTokenAuthenticatorFactory::class,
51
            'args' => []
52
        ],
53
        'httpBasicAuth' => [
54
            'className' => HttpBasicAuthenticator::class,
55
            'args' => [
56
                'realm' => 'Restricted'
57
            ]
58
        ],
59
        'rememberMe' => [
60
            'factoryClass' => RememberMeAuthenticatorFactory::class,
61
            'args' => [
62
                'cookieName' => 'remember_me',
63
                'secret' => 'some-secured-secret'
64
            ]
65
        ],
66
        'formLogin' => [
67
            'factoryClass' => FormLoginAuthenticatorFactory::class,
68
            'args' => []
69
        ]
70
    ];
71
72
    private ?AuthenticationEntryPointInterface $entryPoint = null;
73
74
    /**
75
     * Creates a AuthenticatorsListFactory
76
     *
77
     * @param ContainerInterface $container
78
     * @param array<string, mixed> $properties
79
     */
80
    public function __construct(
81
        private readonly ContainerInterface $container,
82
        array $properties
83
    ) {
84
        foreach ($properties as $name => $config) {
85
            if (!array_key_exists($name, self::$presets)) {
86
                continue;
87
            }
88
89
            $this->createFromFactory($name, $config);
90
        }
91
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96
    
97
    public function getIterator(): Traversable
98
    {
99
        return new ArrayIterator($this->authenticators);
100
    }
101
102
    /**
103
     * @inheritDoc
104
     */
105
    
106
    public function offsetExists(mixed $offset): bool
107
    {
108
        return array_key_exists($offset, $this->authenticators);
109
    }
110
111
    /**
112
     * @inheritDoc
113
     */
114
    
115
    public function offsetGet(mixed $offset): mixed
116
    {
117
        return $this->authenticators[$offset] ?? null;
118
    }
119
120
    /**
121
     * @inheritDoc
122
     */
123
    
124
    public function offsetSet(mixed $offset, mixed $value): void
125
    {
126
        $this->add($offset, $value);
127
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132
    
133
    public function offsetUnset(mixed $offset): void
134
    {
135
        unset($this->authenticators[$offset]);
136
    }
137
138
    /**
139
     * AuthenticatorsListFactory entryPoint
140
     *
141
     * @return AuthenticationEntryPointInterface|null
142
     */
143
    public function entryPoint(): ?AuthenticationEntryPointInterface
144
    {
145
        return $this->entryPoint;
146
    }
147
148
    /**
149
     * Adds an authenticator to the collection.
150
     *
151
     * @param string|int|null $key The key to associate with the authenticator.
152
     * @param AuthenticatorInterface<TUser> $authenticator The authenticator to add.
153
     * @return void
154
     */
155
    private function add(string|int|null $key, AuthenticatorInterface $authenticator): void
156
    {
157
        $this->authenticators[$key] = $authenticator;
158
    }
159
160
    /**
161
     * Creates an authenticator instance from the factory.
162
     *
163
     * If the factory class for the specified name is not defined, it falls back to creating the authenticator instance
164
     * from the container.
165
     *
166
     * @param int|string $name The name of the authenticator.
167
     * @param array<string, mixed> $config The configuration options for the authenticator.
168
     * @return void
169
     */
170
    private function createFromFactory(int|string $name, array $config): void
171
    {
172
        $customKeys = $name === 'custom' ? array_keys($config): [];
173
        $keys = array_merge(array_keys(self::$presets[$name]), $customKeys);
174
175
        if (!in_array('factoryClass', $keys)) {
176
            $this->createFromContainer($name, $config);
177
            return;
178
        }
179
180
        $args = array_merge(self::$presets[$name]['args'] ?? [], $config);
181
        $className = self::$presets[$name]['factoryClass'] ?? $config['factoryClass'];
182
        $factoryCallable = [$className, 'create'];
183
184
        if (is_callable($factoryCallable)) {
185
            $authenticator = call_user_func_array($factoryCallable, [$this->container, $args, $this]);
186
187
            if ($authenticator instanceof AuthenticationEntryPointInterface) {
188
                $this->entryPoint = $authenticator;
189
            }
190
            $this->authenticators[$name] = $authenticator;
191
        }
192
    }
193
194
    /**
195
     * Creates an authenticator instance from the container using the given name and configuration.
196
     *
197
     * @param int|string $name The name of the authenticator.
198
     * @param array<string, mixed> $config The configuration for the authenticator.
199
     * @return void
200
     */
201
    private function createFromContainer(int|string $name, array $config): void
202
    {
203
        $args = $this->parseConstructorArgs($name, $config);
204
        $customClass = isset($config['className']) ? $config['className'] : null;
205
        $className = $customClass ?? self::$presets[$name]['className'];
206
207
        if (isset($config['className'])) {
208
            unset($args['className']);
209
        }
210
211
        $this->authenticators[$name] = $this->container->make($className, ...array_values($args));
212
        if ($this->authenticators[$name] instanceof AuthenticationEntryPointInterface) {
213
            $this->entryPoint = $this->authenticators[$name];
214
        }
215
    }
216
217
    /**
218
     * @param int|string $name
219
     * @param array<string, mixed> $config
220
     * @return array<string, mixed>
221
     */
222
    private function parseConstructorArgs(int|string $name, array $config): array
223
    {
224
        $args = self::$presets[$name]['args'];
225
        if ($name === 'custom' && isset($config['args'])) {
226
            return array_merge($args, $config['args']);
227
        }
228
229
        return array_merge($args, $config);
230
    }
231
232
    /**
233
     * @inheritDoc
234
     */
235
    
236
    public function withEntryPoint(AuthenticationEntryPointInterface $entryPoint): void
237
    {
238
        $this->entryPoint = $entryPoint;
239
    }
240
}
241