slickframework /
configuration
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This file is part of slick/configuration |
||
| 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 | namespace Slick\Configuration; |
||
| 11 | |||
| 12 | use ReflectionClass; |
||
| 13 | use ReflectionException; |
||
| 14 | use Slick\Configuration\Driver\Environment; |
||
| 15 | use Slick\Configuration\Driver\Ini; |
||
| 16 | use Slick\Configuration\Driver\Php; |
||
| 17 | use Slick\Configuration\Exception\InvalidArgumentException; |
||
| 18 | |||
| 19 | /** |
||
| 20 | * Configuration |
||
| 21 | * |
||
| 22 | * @package Slick\Configuration |
||
| 23 | */ |
||
| 24 | final class Configuration |
||
| 25 | { |
||
| 26 | /**@#+ |
||
| 27 | * Known configuration drivers |
||
| 28 | * @var class-string |
||
| 29 | */ |
||
| 30 | const DRIVER_INI = Ini::class; |
||
| 31 | const DRIVER_PHP = Php::class; |
||
| 32 | const DRIVER_ENV = Environment::class; |
||
| 33 | /**@#- */ |
||
| 34 | |||
| 35 | /** @var array<class-string>|string[] */ |
||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
| 36 | private array $extensionToDriver = [ |
||
| 37 | 'ini' => self::DRIVER_INI, |
||
| 38 | 'php' => self::DRIVER_PHP, |
||
| 39 | ]; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * @var string|array<int|string, mixed>|null |
||
| 43 | */ |
||
| 44 | private string|array|null $file; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * @var null|class-string |
||
|
0 ignored issues
–
show
|
|||
| 48 | */ |
||
| 49 | private ?string $driverClass; |
||
| 50 | |||
| 51 | /** @var array<string>|string[] */ |
||
| 52 | private static array $paths = [ |
||
| 53 | './' |
||
| 54 | ]; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * @var null|ConfigurationInterface |
||
| 58 | */ |
||
| 59 | private static ?ConfigurationInterface $instance = null; |
||
| 60 | |||
| 61 | /** |
||
| 62 | * Creates a configuration factory |
||
| 63 | * |
||
| 64 | * @param array<int|string, mixed>|string|null $options |
||
| 65 | * @param null|class-string $driverClass |
||
|
0 ignored issues
–
show
|
|||
| 66 | 18 | */ |
|
| 67 | public function __construct(array|string $options = null, ?string $driverClass = null) |
||
| 68 | 18 | { |
|
| 69 | 12 | $this->file = $options; |
|
| 70 | $this->driverClass = $driverClass; |
||
| 71 | $path = getcwd(); |
||
| 72 | self::addPath(is_string($path) ? $path : './'); |
||
|
0 ignored issues
–
show
|
|||
| 73 | } |
||
| 74 | |||
| 75 | /** |
||
| 76 | * Returns the last ConfigurationInterface |
||
| 77 | 3 | * |
|
| 78 | * If there is no configuration created it will use passed arguments to create one |
||
| 79 | 3 | * |
|
| 80 | 3 | * @param array<int|string, mixed>|string $fileName |
|
| 81 | 3 | * @param null|class-string $driverClass |
|
|
0 ignored issues
–
show
|
|||
| 82 | 3 | * |
|
| 83 | 3 | * @return PriorityConfigurationChain|ConfigurationInterface|null |
|
| 84 | * @throws ReflectionException |
||
| 85 | */ |
||
| 86 | public static function get( |
||
| 87 | array|string $fileName, |
||
| 88 | ?string $driverClass = null |
||
| 89 | ): PriorityConfigurationChain|ConfigurationInterface|null { |
||
| 90 | if (self::$instance === null) { |
||
| 91 | self::$instance = self::create($fileName, $driverClass); |
||
| 92 | } |
||
| 93 | 9 | return self::$instance; |
|
| 94 | } |
||
| 95 | 9 | ||
| 96 | /** |
||
| 97 | 9 | * Creates a ConfigurationInterface with passed arguments |
|
| 98 | 9 | * |
|
| 99 | 9 | * @param array<int|string, mixed>|string $fileName |
|
| 100 | 9 | * @param null|class-string $driverClass |
|
|
0 ignored issues
–
show
|
|||
| 101 | 9 | * |
|
| 102 | * @return ConfigurationInterface|PriorityConfigurationChain |
||
| 103 | * @throws ReflectionException |
||
| 104 | */ |
||
| 105 | public static function create( |
||
| 106 | array|string $fileName, |
||
| 107 | ?string $driverClass = null |
||
| 108 | ): PriorityConfigurationChain|ConfigurationInterface { |
||
| 109 | $configuration = new Configuration($fileName, $driverClass); |
||
| 110 | return $configuration->initialize(); |
||
| 111 | } |
||
| 112 | 9 | ||
| 113 | /** |
||
| 114 | 9 | * @return PriorityConfigurationChain|ConfigurationInterface |
|
| 115 | 9 | * @throws ReflectionException |
|
| 116 | 9 | */ |
|
| 117 | public function initialize(): PriorityConfigurationChain|ConfigurationInterface |
||
| 118 | 9 | { |
|
| 119 | 9 | $chain = new PriorityConfigurationChain(); |
|
| 120 | 9 | ||
| 121 | 3 | $options = $this->fixOptions(); |
|
| 122 | 3 | ||
| 123 | foreach ($options as $option) { |
||
| 124 | 9 | $priority = $this->setProperties($option); |
|
| 125 | 9 | $chain->add($this->createConfigurationDriver(), $priority); |
|
| 126 | } |
||
| 127 | |||
| 128 | return $chain; |
||
| 129 | } |
||
| 130 | |||
| 131 | 18 | /** |
|
| 132 | * Prepends a searchable path to available paths list. |
||
| 133 | 18 | * |
|
| 134 | 3 | * @param string $path |
|
| 135 | 3 | */ |
|
| 136 | 3 | public static function addPath(string $path): void |
|
| 137 | { |
||
| 138 | if (!in_array($path, self::$paths)) { |
||
| 139 | 15 | array_unshift(self::$paths, $path); |
|
| 140 | 15 | } |
|
| 141 | 3 | } |
|
| 142 | 3 | ||
| 143 | /** |
||
| 144 | 3 | * Returns the driver class to be initialized |
|
| 145 | * |
||
| 146 | 12 | * @return class-string |
|
|
0 ignored issues
–
show
|
|||
| 147 | */ |
||
| 148 | private function driverClass(): string |
||
| 149 | { |
||
| 150 | if (null == $this->driverClass) { |
||
|
0 ignored issues
–
show
|
|||
| 151 | $fromArray = is_array($this->file) && isset($this->file[1]) ? $this->file[1] : null; |
||
| 152 | $name = is_array($this->file) ? $fromArray : $this->file; |
||
| 153 | $this->driverClass = $this->determineDriver($name); |
||
| 154 | } |
||
| 155 | return $this->driverClass; |
||
| 156 | } |
||
| 157 | |||
| 158 | /** |
||
| 159 | * Tries to determine the driver class based on given file |
||
| 160 | * |
||
| 161 | * @param string|null $file |
||
| 162 | * @return mixed |
||
| 163 | */ |
||
| 164 | private function determineDriver(?string $file): mixed |
||
| 165 | { |
||
| 166 | $exception = new InvalidArgumentException( |
||
| 167 | "Cannot initialize the configuration driver. I could not determine ". |
||
| 168 | "the correct driver class." |
||
| 169 | ); |
||
| 170 | |||
| 171 | if (is_null($file)) { |
||
| 172 | throw $exception; |
||
| 173 | } |
||
| 174 | |||
| 175 | $nameDivision = explode('.', $file); |
||
| 176 | $extension = strtolower(end($nameDivision)); |
||
| 177 | |||
| 178 | if (!array_key_exists($extension, $this->extensionToDriver)) { |
||
| 179 | throw $exception; |
||
| 180 | } |
||
| 181 | |||
| 182 | return $this->extensionToDriver[$extension]; |
||
| 183 | } |
||
| 184 | |||
| 185 | /** |
||
| 186 | * Compose the filename with existing paths and return when match |
||
| 187 | * |
||
| 188 | * If not matched the $name is returned as is; |
||
| 189 | * If no extension provided it will add it from driver class map; |
||
| 190 | * By default it will try to find <$name>.php file |
||
| 191 | * |
||
| 192 | * @param null|string $name |
||
| 193 | * |
||
| 194 | * @return string|null |
||
| 195 | */ |
||
| 196 | private function composeFileName(?string $name): ?string |
||
| 197 | { |
||
| 198 | if (is_null($name)) { |
||
| 199 | return null; |
||
| 200 | } |
||
| 201 | |||
| 202 | $ext = $this->determineExtension(); |
||
| 203 | $withExtension = $this->createName($name, $ext); |
||
| 204 | |||
| 205 | list($found, $fileName) = $this->searchFor($name, $withExtension); |
||
| 206 | |||
| 207 | return $found ? $fileName : $name; |
||
| 208 | } |
||
| 209 | |||
| 210 | /** |
||
| 211 | * Determine the extension based on the driver class |
||
| 212 | * |
||
| 213 | * If there is no driver class given it will default to .php |
||
| 214 | * |
||
| 215 | * @return string |
||
| 216 | */ |
||
| 217 | private function determineExtension(): string |
||
| 218 | { |
||
| 219 | $ext = 'php'; |
||
| 220 | if (in_array($this->driverClass, $this->extensionToDriver)) { |
||
| 221 | $map = array_flip($this->extensionToDriver); |
||
| 222 | $ext = $map[$this->driverClass]; |
||
| 223 | } |
||
| 224 | return $ext; |
||
| 225 | } |
||
| 226 | |||
| 227 | /** |
||
| 228 | * Creates the name with the extension for known names |
||
| 229 | * |
||
| 230 | * @param string $name |
||
| 231 | * @param string $ext |
||
| 232 | * |
||
| 233 | * @return string |
||
| 234 | */ |
||
| 235 | private function createName(string $name, string $ext): string |
||
| 236 | { |
||
| 237 | $withExtension = $name; |
||
| 238 | if (!preg_match('/.*\.(ini|php)/i', $name)) { |
||
| 239 | $withExtension = "$name.$ext"; |
||
| 240 | } |
||
| 241 | return $withExtension; |
||
| 242 | } |
||
| 243 | |||
| 244 | /** |
||
| 245 | * Search for name in the list of paths |
||
| 246 | * |
||
| 247 | * @param string $name |
||
| 248 | * @param string $withExtension |
||
| 249 | * |
||
| 250 | * @return array{bool, string} |
||
|
0 ignored issues
–
show
|
|||
| 251 | */ |
||
| 252 | private function searchFor(string $name, string $withExtension): array |
||
| 253 | { |
||
| 254 | $found = false; |
||
| 255 | $fileName = $name; |
||
| 256 | |||
| 257 | foreach (self::$paths as $path) { |
||
| 258 | $fileName = "$path/$withExtension"; |
||
| 259 | if (is_file($fileName)) { |
||
| 260 | $found = true; |
||
| 261 | break; |
||
| 262 | } |
||
| 263 | } |
||
| 264 | |||
| 265 | return [$found, $fileName]; |
||
| 266 | } |
||
| 267 | |||
| 268 | /** |
||
| 269 | * Creates the configuration driver from current properties |
||
| 270 | * |
||
| 271 | * @return ConfigurationInterface |
||
| 272 | * @throws ReflectionException |
||
| 273 | */ |
||
| 274 | private function createConfigurationDriver(): ConfigurationInterface |
||
| 275 | { |
||
| 276 | $reflection = new ReflectionClass($this->driverClass()); |
||
| 277 | |||
| 278 | /** @var ConfigurationInterface $config */ |
||
| 279 | $config = $reflection->hasMethod('__construct') |
||
| 280 | ? $reflection->newInstanceArgs([$this->file]) |
||
| 281 | : $reflection->newInstance(); |
||
| 282 | return $config; |
||
| 283 | } |
||
| 284 | |||
| 285 | /** |
||
| 286 | * Sets the file and driver class |
||
| 287 | * |
||
| 288 | * @param array{?string, ?class-string, ?int} $option |
||
|
0 ignored issues
–
show
|
|||
| 289 | * |
||
| 290 | * @return int |
||
| 291 | */ |
||
| 292 | private function setProperties(array $option): int |
||
| 293 | { |
||
| 294 | $priority = $option[2] ?? 0; |
||
| 295 | $this->driverClass = $option[1] ?? null; |
||
| 296 | $this->file = isset($option[0]) ? $this->composeFileName($option[0]) : null; |
||
| 297 | return $priority; |
||
| 298 | } |
||
| 299 | |||
| 300 | /** |
||
| 301 | * Fixes the file for initialization |
||
| 302 | * |
||
| 303 | * @return array<int|string, mixed> |
||
| 304 | */ |
||
| 305 | private function fixOptions(): array |
||
| 306 | { |
||
| 307 | return (is_array($this->file)) |
||
| 308 | ? $this->file |
||
| 309 | : [[$this->file]]; |
||
| 310 | } |
||
| 311 | } |
||
| 312 |