1<?php
2
3/**
4 * This file is part of the FreeDSx LDAP package.
5 *
6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace FreeDSx\Ldap\Server\RequestHandler;
13
14use FreeDSx\Ldap\Exception\RuntimeException;
15use FreeDSx\Ldap\Server\HandlerFactoryInterface;
16use Throwable;
17
18/**
19 * This is used by the server protocol handler to instantiate the possible user-land LDAP handlers (ie. handlers exposed
20 * in the public API options).
21 *
22 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
23 */
24class HandlerFactory implements HandlerFactoryInterface
25{
26    /**
27     * @var RequestHandlerInterface|null
28     */
29    private $requestHandler;
30
31    /**
32     * @var RootDseHandlerInterface|null
33     */
34    private $rootdseHandler;
35
36    /**
37     * @var PagingHandlerInterface|null
38     */
39    private $pagingHandler;
40
41    /**
42     * @var array<string, mixed>
43     */
44    private $options;
45
46    /**
47     * @param array<string, mixed> $options
48     */
49    public function __construct(array $options)
50    {
51        $this->options = $options;
52    }
53
54    /**
55     * @inheritDoc
56     */
57    public function makeRequestHandler(): RequestHandlerInterface
58    {
59        if (!$this->requestHandler) {
60            $requestHandler = !isset($this->options['request_handler'])
61                ? new GenericRequestHandler()
62                : $this->makeOrReturnInstanceOf(
63                    'request_handler',
64                    RequestHandlerInterface::class
65                );
66            if (!$requestHandler instanceof RequestHandlerInterface) {
67                throw new RuntimeException(sprintf(
68                    'Expected an instance of %s, got: %s',
69                    RequestHandlerInterface::class,
70                    get_class($requestHandler)
71                ));
72            }
73            $this->requestHandler = $requestHandler;
74        }
75
76        return $this->requestHandler;
77    }
78
79    /**
80     * @inheritDoc
81     */
82    public function makeRootDseHandler(): ?RootDseHandlerInterface
83    {
84        if ($this->rootdseHandler) {
85            return $this->rootdseHandler;
86        }
87        $handler = $this->makeRequestHandler();
88        $this->rootdseHandler = $handler instanceof RootDseHandlerInterface
89            ? $handler
90            : null;
91
92        if ($this->rootdseHandler) {
93            return $this->rootdseHandler;
94        }
95
96        if (isset($this->options['rootdse_handler'])) {
97            $handler = $this->makeOrReturnInstanceOf(
98                'rootdse_handler',
99                RootDseHandlerInterface::class
100            );
101        }
102
103        if ($handler instanceof RootDseHandlerInterface) {
104            $this->rootdseHandler = $handler;
105        }
106
107        return $this->rootdseHandler;
108    }
109
110    /**
111     * @inheritDoc
112     */
113    public function makePagingHandler(): ?PagingHandlerInterface
114    {
115        if ($this->pagingHandler) {
116            return $this->pagingHandler;
117        }
118
119        $handler = null;
120        if (isset($this->options['paging_handler'])) {
121            $handler = $this->makeOrReturnInstanceOf(
122                'paging_handler',
123                PagingHandlerInterface::class
124            );
125        }
126
127        if ($handler !== null && !$handler instanceof PagingHandlerInterface) {
128            throw new RuntimeException(sprintf(
129                'Expected an instance of %s, got: %s',
130                PagingHandlerInterface::class,
131                get_class($handler)
132            ));
133        }
134        $this->pagingHandler = $handler;
135
136        return $this->pagingHandler;
137    }
138
139    /**
140     * @param string $optionName
141     * @param class-string $class
142     * @return object
143     */
144    private function makeOrReturnInstanceOf(
145        string $optionName,
146        string $class
147    ) {
148        if (!isset($this->options[$optionName])) {
149            throw new RuntimeException(sprintf(
150                'Option "%s" must be an instance of or fully qualified class-name implementing "%s".',
151                $optionName,
152                $class
153            ));
154        }
155
156        $objOrString = $this->options[$optionName];
157        if (!(is_object($objOrString) || is_string($objOrString))) {
158            throw new RuntimeException(sprintf(
159                'Option "%s" must be an instance of or fully qualified class-name implementing "%s".',
160                $optionName,
161                $class
162            ));
163        }
164
165        if (is_object($objOrString) && is_subclass_of($objOrString, $class)) {
166            return $objOrString;
167        }
168
169        if (is_string($objOrString) && is_subclass_of($objOrString, $class)) {
170            try {
171                return new $objOrString();
172            } catch (Throwable $e) {
173                throw new RuntimeException(sprintf(
174                    'Unable to instantiate class "%s" for option "%s": %s',
175                    $objOrString,
176                    $optionName,
177                    $e->getMessage()
178                ), $e->getCode(), $e);
179            }
180        }
181
182        throw new RuntimeException(sprintf(
183            'Option "%s" must be an instance of or fully qualified class-name implementing "%s".',
184            $optionName,
185            $class
186        ));
187    }
188}
189