Skip to content

Commit 36b530c

Browse files
authored
refactor: Throw more friendly exceptions when loading a configuration (#1045)
Make the configuration and its factory throw more comprehensible exceptions. This also makes the switch from `InvalidArgumentException` to `UnexpectedValueException` which is more correct IMO as it is values provided by the user.
1 parent 3224161 commit 36b530c

12 files changed

+323
-131
lines changed

src/Configuration/Configuration.php

+7-14
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414

1515
namespace Humbug\PhpScoper\Configuration;
1616

17+
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue;
1718
use Humbug\PhpScoper\Patcher\Patcher;
18-
use InvalidArgumentException;
1919
use function Safe\preg_match;
20-
use function sprintf;
2120

2221
final class Configuration
2322
{
@@ -38,6 +37,8 @@ final class Configuration
3837
* @param array<string, array{string, string}> $excludedFilesWithContents Array of tuple
3938
* with the first argument being the file path and
4039
* the second its contents
40+
*
41+
* @throws InvalidConfigurationValue
4142
*/
4243
public function __construct(
4344
private ?string $path,
@@ -71,6 +72,8 @@ public function getOutputDir(): ?string
7172

7273
/**
7374
* @param non-empty-string $prefix
75+
*
76+
* @throws InvalidConfigurationValue
7477
*/
7578
public function withPrefix(string $prefix): self
7679
{
@@ -151,21 +154,11 @@ public function getSymbolsConfiguration(): SymbolsConfiguration
151154
private static function validatePrefix(string $prefix): void
152155
{
153156
if (1 !== preg_match(self::PREFIX_PATTERN, $prefix)) {
154-
throw new InvalidArgumentException(
155-
sprintf(
156-
'The prefix needs to be composed solely of letters, digits and backslashes (as namespace separators). Got "%s"',
157-
$prefix,
158-
),
159-
);
157+
throw InvalidConfigurationValue::forInvalidPrefixPattern($prefix);
160158
}
161159

162160
if (preg_match('/\\\{2,}/', $prefix)) {
163-
throw new InvalidArgumentException(
164-
sprintf(
165-
'Invalid namespace separator sequence. Got "%s"',
166-
$prefix,
167-
),
168-
);
161+
throw InvalidConfigurationValue::forInvalidNamespaceSeparator($prefix);
169162
}
170163
}
171164
}

src/Configuration/ConfigurationFactory.php

+36-108
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414

1515
namespace Humbug\PhpScoper\Configuration;
1616

17+
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfiguration;
18+
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationFile;
19+
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue;
1720
use Humbug\PhpScoper\Patcher\ComposerPatcher;
1821
use Humbug\PhpScoper\Patcher\Patcher;
1922
use Humbug\PhpScoper\Patcher\PatcherChain;
2023
use Humbug\PhpScoper\Patcher\SymfonyParentTraitPatcher;
2124
use Humbug\PhpScoper\Patcher\SymfonyPatcher;
22-
use InvalidArgumentException;
23-
use RuntimeException;
2425
use SplFileInfo;
2526
use Symfony\Component\Filesystem\Filesystem;
2627
use Symfony\Component\Finder\Finder;
@@ -33,9 +34,7 @@
3334
use function bin2hex;
3435
use function dirname;
3536
use function file_exists;
36-
use function gettype;
3737
use function Humbug\PhpScoper\chain;
38-
use function in_array;
3938
use function is_array;
4039
use function is_callable;
4140
use function is_dir;
@@ -47,7 +46,6 @@
4746
use function readlink as native_readlink;
4847
use function realpath;
4948
use function Safe\file_get_contents;
50-
use function sprintf;
5149
use function trim;
5250
use const DIRECTORY_SEPARATOR;
5351

@@ -64,14 +62,12 @@ public function __construct(
6462
/**
6563
* @param non-empty-string|null $path Absolute canonical path to the configuration file.
6664
* @param list<non-empty-string> $paths List of absolute canonical paths to append besides the one configured
65+
*
66+
* @throws InvalidConfiguration
6767
*/
6868
public function create(?string $path = null, array $paths = []): Configuration
6969
{
70-
if (null === $path) {
71-
$config = [];
72-
} else {
73-
$config = $this->loadConfigFile($path);
74-
}
70+
$config = null === $path ? [] : $this->loadConfigFile($path);
7571

7672
self::validateConfigKeys($config);
7773

@@ -134,48 +130,31 @@ public function createWithPrefix(Configuration $config, string $prefix): Configu
134130
return $config->withPrefix($prefix);
135131
}
136132

133+
/**
134+
* @throws InvalidConfigurationValue
135+
*/
137136
private function loadConfigFile(string $path): array
138137
{
139138
if (!$this->fileSystem->isAbsolutePath($path)) {
140-
throw new InvalidArgumentException(
141-
sprintf(
142-
'Expected the path of the configuration file to load to be an absolute path, got "%s" instead',
143-
$path,
144-
),
145-
);
139+
throw InvalidConfigurationFile::forNonAbsolutePath($path);
146140
}
147141

148142
if (!file_exists($path)) {
149-
throw new InvalidArgumentException(
150-
sprintf(
151-
'Expected the path of the configuration file to exists but the file "%s" could not be found',
152-
$path,
153-
),
154-
);
143+
throw InvalidConfigurationFile::forFileNotFound($path);
155144
}
156145

157146
$isADirectoryLink = is_link($path)
158147
&& false !== native_readlink($path)
159148
&& is_file(native_readlink($path));
160149

161150
if (!$isADirectoryLink && !is_file($path)) {
162-
throw new InvalidArgumentException(
163-
sprintf(
164-
'Expected the path of the configuration file to be a file but "%s" appears to be a directory.',
165-
$path,
166-
),
167-
);
151+
throw InvalidConfigurationFile::forNotAFile($path);
168152
}
169153

170154
$config = include $path;
171155

172156
if (!is_array($config)) {
173-
throw new InvalidArgumentException(
174-
sprintf(
175-
'Expected configuration to be an array, found "%s" instead.',
176-
gettype($config),
177-
),
178-
);
157+
throw InvalidConfigurationFile::forInvalidValue($path);
179158
}
180159

181160
return $config;
@@ -184,25 +163,11 @@ private function loadConfigFile(string $path): array
184163
private static function validateConfigKeys(array $config): void
185164
{
186165
array_map(
187-
static fn (string $key) => self::validateConfigKey($key),
166+
ConfigurationKeys::assertIsValidKey(...),
188167
array_keys($config),
189168
);
190169
}
191170

192-
private static function validateConfigKey(string $key): void
193-
{
194-
if (in_array($key, ConfigurationKeys::KEYWORDS, true)) {
195-
return;
196-
}
197-
198-
throw new InvalidArgumentException(
199-
sprintf(
200-
'Invalid configuration key value "%s" found.',
201-
$key,
202-
),
203-
);
204-
}
205-
206171
/**
207172
* @return non-empty-string
208173
*/
@@ -224,6 +189,8 @@ private static function retrieveOutputDir(array $config): ?string
224189
}
225190

226191
/**
192+
* @throws InvalidConfigurationValue
193+
*
227194
* @return array<(callable(string,string,string): string)|Patcher>
228195
*/
229196
private static function retrievePatchers(array $config): array
@@ -235,31 +202,21 @@ private static function retrievePatchers(array $config): array
235202
$patchers = $config[ConfigurationKeys::PATCHERS_KEYWORD];
236203

237204
if (!is_array($patchers)) {
238-
throw new InvalidArgumentException(
239-
sprintf(
240-
'Expected patchers to be an array of callables, found "%s" instead.',
241-
gettype($patchers),
242-
),
243-
);
205+
throw InvalidConfigurationValue::forInvalidPatchersType($patchers);
244206
}
245207

246208
foreach ($patchers as $index => $patcher) {
247-
if (is_callable($patcher)) {
248-
continue;
209+
if (!is_callable($patcher)) {
210+
throw InvalidConfigurationValue::forInvalidPatcherType($index, $patcher);
249211
}
250-
251-
throw new InvalidArgumentException(
252-
sprintf(
253-
'Expected patchers to be an array of callables, the "%d" element is not.',
254-
$index,
255-
),
256-
);
257212
}
258213

259214
return $patchers;
260215
}
261216

262217
/**
218+
* @throws InvalidConfigurationValue
219+
*
263220
* @return string[] Absolute paths
264221
*/
265222
private function retrieveExcludedFiles(string $dirPath, array $config): array
@@ -271,22 +228,12 @@ private function retrieveExcludedFiles(string $dirPath, array $config): array
271228
$excludedFiles = $config[ConfigurationKeys::EXCLUDED_FILES_KEYWORD];
272229

273230
if (!is_array($excludedFiles)) {
274-
throw new InvalidArgumentException(
275-
sprintf(
276-
'Expected excluded files to be an array of strings, found "%s" instead.',
277-
gettype($excludedFiles),
278-
),
279-
);
231+
throw InvalidConfigurationValue::forInvalidExcludedFilesTypes($excludedFiles);
280232
}
281233

282234
foreach ($excludedFiles as $index => $file) {
283235
if (!is_string($file)) {
284-
throw new InvalidArgumentException(
285-
sprintf(
286-
'Expected excluded files to be an array of string, the "%d" element is not.',
287-
$index,
288-
),
289-
);
236+
throw InvalidConfigurationValue::forInvalidExcludedFilePath($index, $excludedFiles);
290237
}
291238

292239
if (!$this->fileSystem->isAbsolutePath($file)) {
@@ -296,10 +243,14 @@ private function retrieveExcludedFiles(string $dirPath, array $config): array
296243
$excludedFiles[$index] = realpath($file);
297244
}
298245

246+
// We ignore files not found excluded file as we do not want to bail out just because a file we do not want to
247+
// include does not exist.
299248
return array_filter($excludedFiles);
300249
}
301250

302251
/**
252+
* @throws InvalidConfigurationValue
253+
*
303254
* @return Finder[]
304255
*/
305256
private static function retrieveFinders(array $config): array
@@ -311,27 +262,15 @@ private static function retrieveFinders(array $config): array
311262
$finders = $config[ConfigurationKeys::FINDER_KEYWORD];
312263

313264
if (!is_array($finders)) {
314-
throw new InvalidArgumentException(
315-
sprintf(
316-
'Expected finders to be an array of "%s", found "%s" instead.',
317-
Finder::class,
318-
gettype($finders),
319-
),
320-
);
265+
throw InvalidConfigurationValue::forInvalidFinderTypes($finders);
321266
}
322267

323268
foreach ($finders as $index => $finder) {
324269
if ($finder instanceof Finder) {
325270
continue;
326271
}
327272

328-
throw new InvalidArgumentException(
329-
sprintf(
330-
'Expected finders to be an array of "%s", the "%d" element is not.',
331-
Finder::class,
332-
$index,
333-
),
334-
);
273+
throw InvalidConfigurationValue::forInvalidFinderType($index, $finder);
335274
}
336275

337276
return $finders;
@@ -340,6 +279,8 @@ private static function retrieveFinders(array $config): array
340279
/**
341280
* @param string[] $paths
342281
*
282+
* @throws InvalidConfigurationValue
283+
*
343284
* @return iterable<SplFileInfo>
344285
*/
345286
private static function retrieveFilesFromPaths(array $paths): iterable
@@ -353,12 +294,7 @@ private static function retrieveFilesFromPaths(array $paths): iterable
353294

354295
foreach ($paths as $path) {
355296
if (!file_exists($path)) {
356-
throw new RuntimeException(
357-
sprintf(
358-
'Could not find the file "%s".',
359-
$path,
360-
),
361-
);
297+
throw InvalidConfigurationValue::forFileNotFound($path);
362298
}
363299

364300
if (is_dir($path)) {
@@ -384,6 +320,8 @@ private static function retrieveFilesFromPaths(array $paths): iterable
384320
/**
385321
* @param iterable<SplFileInfo|string> $files
386322
*
323+
* @throws InvalidConfigurationValue
324+
*
387325
* @return array<string, array{string, string}> Array of tuple with the first argument being the file path and the second its contents
388326
*/
389327
private static function retrieveFilesWithContents(iterable $files): array
@@ -396,21 +334,11 @@ private static function retrieveFilesWithContents(iterable $files): array
396334
: realpath($filePathOrFileInfo);
397335

398336
if (!$filePath) {
399-
throw new RuntimeException(
400-
sprintf(
401-
'Could not find the file "%s".',
402-
(string) $filePathOrFileInfo,
403-
),
404-
);
337+
throw InvalidConfigurationValue::forFileNotFound((string) $filePathOrFileInfo);
405338
}
406339

407340
if (!is_readable($filePath)) {
408-
throw new RuntimeException(
409-
sprintf(
410-
'Could not read the file "%s".',
411-
$filePath,
412-
),
413-
);
341+
throw InvalidConfigurationValue::forUnreadableFile($filePath);
414342
}
415343

416344
$filesWithContents[$filePath] = [$filePath, file_get_contents($filePath)];

src/Configuration/ConfigurationKeys.php

+16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
namespace Humbug\PhpScoper\Configuration;
1616

17+
use Humbug\PhpScoper\Configuration\Throwable\UnknownConfigurationKey;
1718
use Humbug\PhpScoper\NotInstantiable;
1819

1920
final class ConfigurationKeys
@@ -58,4 +59,19 @@ final class ConfigurationKeys
5859
self::FUNCTIONS_INTERNAL_SYMBOLS_KEYWORD,
5960
self::CONSTANTS_INTERNAL_SYMBOLS_KEYWORD,
6061
];
62+
63+
/**
64+
* @throws UnknownConfigurationKey
65+
*/
66+
public static function assertIsValidKey(string $key): void
67+
{
68+
if (!self::isValidateKey($key)) {
69+
throw UnknownConfigurationKey::forKey($key);
70+
}
71+
}
72+
73+
public static function isValidateKey(string $key): bool
74+
{
75+
return in_array($key, self::KEYWORDS, true);
76+
}
6177
}

0 commit comments

Comments
 (0)