packages/deriver/src/Configuration/Configuration.php
1<?php
2
3declare(strict_types=1);
4
5namespace Deriver\Configuration;
6
7use Deriver\Exception\ConfigurationException;
8
9/**
10 * Validated command settings, independent of the preparation and evaluation layers.
11 * @visibility public
12 * @example Reading command defaults
13 * \Deriver\Configuration\Configuration::fromArray([])->paths // => ['.']
14 */
15final class Configuration
16{
17 /**
18 * @var array<string, scalar|array<array-key, mixed>|null> Explicit native variable and environment inputs
19 */
20 public readonly array $environment;
21
22 /**
23 * @param list<string> $paths Search roots
24 * @param list<string> $include Root-relative wildcard patterns
25 * @param list<string> $exclude Root-relative wildcard patterns
26 * @throws ConfigurationException If source selection is empty or a language setting is outside its supported range
27 */
28 private function __construct(
29 public readonly array $paths,
30 public readonly array $include,
31 public readonly array $exclude,
32 public readonly ?string $phpVersion,
33 public readonly int $integerBits,
34 public readonly ?int $floatPrecision,
35 Environment $environment,
36 public readonly bool $standardEvaluators,
37 public readonly ReportFormat $format,
38 ) {
39 if ($paths === [] || $include === []) {
40 throw new ConfigurationException('paths and include must each contain at least one entry.');
41 }
42 if ($phpVersion !== null && preg_match('/\A[1-9][0-9]*\.[0-9]+\z/', $phpVersion) !== 1) {
43 throw new ConfigurationException('phpVersion must use major.minor notation.');
44 }
45 if ($integerBits !== 64 || $floatPrecision !== null && $floatPrecision < -1) {
46 throw new ConfigurationException('integerBits must be 64 and floatPrecision must be null or an integer >= -1.');
47 }
48 $this->environment = $environment->values;
49 }
50
51 /**
52 * Validates a settings mapping without reading files or constructing analysis objects.
53 * @param array<array-key, mixed> $settings Settings decoded from YAML or supplied by a caller
54 * @throws ConfigurationException If keys or values do not match the command schema
55 */
56 public static function fromArray(array $settings): self
57 {
58 $defaults = ['paths' => ['.'], 'include' => ['**/*.php'], 'exclude' => [], 'phpVersion' => null, 'integerBits' => 64, 'floatPrecision' => null, 'environment' => [], 'standardEvaluators' => true, 'format' => 'text'];
59 foreach ($settings as $key => $value) {
60 if (!array_key_exists($key, $defaults)) {
61 throw new ConfigurationException('Unknown configuration key: ' . $key);
62 }
63 }
64 $settings = array_replace($defaults, $settings);
65 $patterns = [];
66 foreach (['paths', 'include', 'exclude'] as $key) {
67 $items = $settings[$key];
68 if (!is_array($items) || !array_is_list($items)) {
69 throw new ConfigurationException($key . ' must be a list of strings.');
70 }
71 $patterns[$key] = [];
72 foreach ($items as $item) {
73 if (!is_string($item) || trim($item) === '' || str_contains($item, "\0")) {
74 throw new ConfigurationException($key . ' must contain nonempty strings without null bytes.');
75 }
76 $patterns[$key][] = $item;
77 }
78 }
79 $version = $settings['phpVersion'];
80 $precision = $settings['floatPrecision'];
81 if ($version !== null && !is_string($version)) {
82 throw new ConfigurationException('phpVersion must be a quoted major.minor string.');
83 }
84 if (!is_int($settings['integerBits']) || $precision !== null && !is_int($precision) || !is_bool($settings['standardEvaluators'])) {
85 throw new ConfigurationException('integerBits must be 64, floatPrecision null or an integer >= -1, and standardEvaluators a boolean.');
86 }
87 if (!is_array($settings['environment']) || !is_string($settings['format']) || ($format = ReportFormat::tryFrom($settings['format'])) === null) {
88 throw new ConfigurationException('environment must be a mapping and format must be text or json.');
89 }
90 $environment = new Environment($settings['environment']);
91 return new self($patterns['paths'], $patterns['include'], $patterns['exclude'], $version, $settings['integerBits'], $precision, $environment, $settings['standardEvaluators'], $format);
92 }
93}
94