Skip to content

Commit f05f0a1

Browse files
authored
Add files whitelist feature (#262)
Closes #196
1 parent f595614 commit f05f0a1

File tree

7 files changed

+263
-9
lines changed

7 files changed

+263
-9
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ potentially very difficult to debug due to dissimilar or unsupported package ver
3737
- [Prefix](#prefix)
3838
- [Finders and paths](#finders-and-paths)
3939
- [Patchers](#patchers)
40+
- [Whitelisted files](#whitelisted-files)
4041
- [Whitelist][whitelist]
4142
- [Constants & functions from the global namespace](#constants--functions-from-the-global-namespace)
4243
- [Symbols](#symbols)
@@ -135,6 +136,7 @@ return [
135136
'prefix' => null, // string|null
136137
'finders' => [], // Finder[]
137138
'patchers' => [], // callable[]
139+
'whitelisted-files' => [], // string[]
138140
'whitelist' => [], // string[]
139141
'whitelist-global-constants' => true, // bool
140142
'whitelist-global-classes' => true, // bool
@@ -262,6 +264,12 @@ return [
262264
```
263265

264266

267+
### Whitelisted files
268+
269+
For the files listed in `whitelisted-files`, their content will be left
270+
untouched during the scoping process.
271+
272+
265273
### Whitelist
266274

267275
PHP-Scoper's goal is to make sure that all code for a project lies in a

src/Configuration.php

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,22 @@
2020
use RuntimeException;
2121
use SplFileInfo;
2222
use Symfony\Component\Finder\Finder;
23+
use const DIRECTORY_SEPARATOR;
24+
use function dirname;
25+
use function gettype;
26+
use function is_array;
2327
use function is_bool;
28+
use function is_string;
29+
use function realpath;
2430

2531
/**
2632
* @final
2733
* TODO: make this class as final as soon as the underlying deprecated class is removed.
2834
*/
2935
class Configuration
3036
{
31-
private const PREFIX = 'prefix';
37+
private const PREFIX_KEYWORD = 'prefix';
38+
private const WHITELISTED_FILES_KEYWORD = 'files-whitelist';
3239
private const FINDER_KEYWORD = 'finders';
3340
private const PATCHERS_KEYWORD = 'patchers';
3441
private const WHITELIST_KEYWORD = 'whitelist';
@@ -37,10 +44,13 @@ class Configuration
3744
private const WHITELIST_GLOBAL_FUNCTIONS_KEYWORD = 'whitelist-global-functions';
3845

3946
private const KEYWORDS = [
40-
self::PREFIX,
47+
self::PREFIX_KEYWORD,
48+
self::WHITELISTED_FILES_KEYWORD,
4149
self::FINDER_KEYWORD,
4250
self::PATCHERS_KEYWORD,
4351
self::WHITELIST_KEYWORD,
52+
self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD,
53+
self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
4454
self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
4555
];
4656

@@ -49,6 +59,7 @@ class Configuration
4959
private $filesWithContents;
5060
private $patchers;
5161
private $whitelist;
62+
private $whitelistedFiles;
5263

5364
/**
5465
* @param string|null $path Absolute path to the configuration file.
@@ -77,14 +88,16 @@ public static function load(string $path = null, array $paths = []): self
7788

7889
$prefix = self::retrievePrefix($config);
7990

91+
$whitelistedFiles = null === $path ? [] : self::retrieveWhitelistedFiles(dirname($path), $config);
92+
8093
$patchers = self::retrievePatchers($config);
8194
$whitelist = self::retrieveWhitelist($config);
8295

8396
$finders = self::retrieveFinders($config);
8497
$filesFromPaths = self::retrieveFilesFromPaths($paths);
8598
$filesWithContents = self::retrieveFilesWithContents(chain($filesFromPaths, ...$finders));
8699

87-
return new self($path, $prefix, $filesWithContents, $patchers, $whitelist);
100+
return new self($path, $prefix, $filesWithContents, $patchers, $whitelist, $whitelistedFiles);
88101
}
89102

90103
/**
@@ -97,19 +110,22 @@ public static function load(string $path = null, array $paths = []): self
97110
* @param Closure $globalNamespaceWhitelisters Closure taking a class name from the global namespace as an argument and
98111
* returning a boolean which if `true` means the class should be scoped
99112
* (i.e. is ignored) or scoped otherwise.
113+
* @param string[] $whitelistedFiles List of absolute paths of files to completely ignore
100114
*/
101115
private function __construct(
102116
?string $path,
103117
?string $prefix,
104118
array $filesWithContents,
105119
array $patchers,
106-
Whitelist $whitelist
120+
Whitelist $whitelist,
121+
array $whitelistedFiles
107122
) {
108123
$this->path = $path;
109124
$this->prefix = $prefix;
110125
$this->filesWithContents = $filesWithContents;
111126
$this->patchers = $patchers;
112127
$this->whitelist = $whitelist;
128+
$this->whitelistedFiles = $whitelistedFiles;
113129
}
114130

115131
public function withPaths(array $paths): self
@@ -127,20 +143,22 @@ public function withPaths(array $paths): self
127143
$this->prefix,
128144
array_merge($this->filesWithContents, $filesWithContents),
129145
$this->patchers,
130-
$this->whitelist
146+
$this->whitelist,
147+
$this->whitelistedFiles
131148
);
132149
}
133150

134151
public function withPrefix(?string $prefix): self
135152
{
136-
$prefix = self::retrievePrefix([self::PREFIX => $prefix]);
153+
$prefix = self::retrievePrefix([self::PREFIX_KEYWORD => $prefix]);
137154

138155
return new self(
139156
$this->path,
140157
$prefix,
141158
$this->filesWithContents,
142159
$this->patchers,
143-
$this->whitelist
160+
$this->whitelist,
161+
$this->whitelistedFiles
144162
);
145163
}
146164

@@ -172,6 +190,14 @@ public function getWhitelist(): Whitelist
172190
return $this->whitelist;
173191
}
174192

193+
/**
194+
* @return string[]
195+
*/
196+
public function getWhitelistedFiles(): array
197+
{
198+
return $this->whitelistedFiles;
199+
}
200+
175201
private static function validateConfigKeys(array $config): void
176202
{
177203
array_map(
@@ -200,7 +226,7 @@ private static function validateConfigKey(string $key): void
200226
*/
201227
private static function retrievePrefix(array $config): ?string
202228
{
203-
$prefix = array_key_exists(self::PREFIX, $config) ? $config[self::PREFIX] : null;
229+
$prefix = array_key_exists(self::PREFIX_KEYWORD, $config) ? $config[self::PREFIX_KEYWORD] : null;
204230

205231
if (null === $prefix) {
206232
return null;
@@ -325,6 +351,46 @@ private static function retrieveWhitelist(array $config): Whitelist
325351
return Whitelist::create($whitelistGlobalConstants, $whitelistGlobalClasses, $whitelistGlobalFunctions, ...$whitelist);
326352
}
327353

354+
/**
355+
* @return string[] Absolute paths
356+
*/
357+
private static function retrieveWhitelistedFiles(string $dirPath, array $config): array
358+
{
359+
if (false === array_key_exists(self::WHITELISTED_FILES_KEYWORD, $config)) {
360+
return [];
361+
}
362+
363+
$whitelistedFiles = $config[self::WHITELIST_KEYWORD];
364+
365+
if (false === is_array($whitelistedFiles)) {
366+
throw new InvalidArgumentException(
367+
sprintf(
368+
'Expected whitelisted files to be an array of strings, found "%s" instead.',
369+
gettype($whitelistedFiles)
370+
)
371+
);
372+
}
373+
374+
foreach ($whitelistedFiles as $index => $file) {
375+
if (is_string($file)) {
376+
throw new InvalidArgumentException(
377+
sprintf(
378+
'Expected whitelisted files to be an array of string, the "%d" element is not.',
379+
$index
380+
)
381+
);
382+
}
383+
384+
if ('' !== $file && DIRECTORY_SEPARATOR !== $file[0]) {
385+
$file = $dirPath.DIRECTORY_SEPARATOR.$file;
386+
}
387+
388+
$whitelistedFiles[$index] = realpath($file);
389+
}
390+
391+
return array_filter($whitelistedFiles);
392+
}
393+
328394
private static function retrieveFinders(array $config): array
329395
{
330396
if (false === array_key_exists(self::FINDER_KEYWORD, $config)) {

src/Console/Command/AddPrefixCommand.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Humbug\PhpScoper\Configuration;
1919
use Humbug\PhpScoper\Logger\ConsoleLogger;
2020
use Humbug\PhpScoper\Scoper;
21+
use Humbug\PhpScoper\Scoper\ConfigurableScoper;
2122
use Humbug\PhpScoper\Throwable\Exception\ParsingException;
2223
use Humbug\PhpScoper\Whitelist;
2324
use Symfony\Component\Console\Exception\RuntimeException;
@@ -30,6 +31,7 @@
3031
use Symfony\Component\Console\Style\SymfonyStyle;
3132
use Symfony\Component\Filesystem\Filesystem;
3233
use Throwable;
34+
use function count;
3335
use function Humbug\PhpScoper\get_common_path;
3436

3537
final class AddPrefixCommand extends BaseCommand
@@ -55,7 +57,7 @@ public function __construct(Filesystem $fileSystem, Scoper $scoper)
5557
parent::__construct();
5658

5759
$this->fileSystem = $fileSystem;
58-
$this->scoper = $scoper;
60+
$this->scoper = new ConfigurableScoper($scoper);
5961
}
6062

6163
/**
@@ -133,6 +135,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
133135
$config = $this->retrieveConfig($input, $output, $io);
134136
$output = $input->getOption(self::OUTPUT_DIR_OPT);
135137

138+
if ([] !== $config->getWhitelistedFiles()) {
139+
$this->scoper = $this->scoper->withWhitelistedFiles(...$config->getWhitelistedFiles());
140+
}
141+
136142
$logger = new ConsoleLogger(
137143
$this->getApplication(),
138144
$io

src/Scoper/ConfigurableScoper.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the humbug/php-scoper package.
7+
*
8+
* Copyright (c) 2017 Théo FIDRY <[email protected]>,
9+
* Pádraic Brady <[email protected]>
10+
*
11+
* For the full copyright and license information, please view the LICENSE
12+
* file that was distributed with this source code.
13+
*/
14+
15+
namespace Humbug\PhpScoper\Scoper;
16+
17+
use Humbug\PhpScoper\Scoper;
18+
use Humbug\PhpScoper\Whitelist;
19+
20+
final class ConfigurableScoper implements Scoper
21+
{
22+
private $decoratedScoper;
23+
24+
public function __construct(Scoper $decoratedScoper)
25+
{
26+
$this->decoratedScoper = $decoratedScoper;
27+
}
28+
29+
public function withWhitelistedFiles(string ...$whitelistedFiles): self
30+
{
31+
$self = clone $this;
32+
33+
return [] === $whitelistedFiles ? $self : new self(new FileWhitelistScoper($self));
34+
}
35+
36+
/**
37+
* @inheritdoc
38+
*/
39+
public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string
40+
{
41+
return $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist);
42+
}
43+
}

src/Scoper/FileWhitelistScoper.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the humbug/php-scoper package.
7+
*
8+
* Copyright (c) 2017 Théo FIDRY <[email protected]>,
9+
* Pádraic Brady <[email protected]>
10+
*
11+
* For the full copyright and license information, please view the LICENSE
12+
* file that was distributed with this source code.
13+
*/
14+
15+
namespace Humbug\PhpScoper\Scoper;
16+
17+
use Humbug\PhpScoper\Scoper;
18+
use Humbug\PhpScoper\Whitelist;
19+
use function array_flip;
20+
use function array_key_exists;
21+
22+
final class FileWhitelistScoper implements Scoper
23+
{
24+
private $decoratedScoper;
25+
private $filePaths;
26+
27+
public function __construct(Scoper $decoratedScoper, string ...$filePaths)
28+
{
29+
$this->decoratedScoper = $decoratedScoper;
30+
$this->filePaths = array_flip($filePaths);
31+
}
32+
33+
/**
34+
* @inheritdoc
35+
*/
36+
public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string
37+
{
38+
if (array_key_exists($filePath, $this->filePaths)) {
39+
return $contents;
40+
}
41+
42+
return $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist);
43+
}
44+
}

src/scoper.inc.php.tpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ return [
3333
]),
3434
],
3535

36+
// Whitelists a list of files. Unlike the other whitelist related features, this one is about completely leaving
37+
// a file untouched.
38+
// Paths are relative to the configuration file unless if they are already absolute
39+
'files-whitelist' => [
40+
'src/a-whitelisted-file.php',
41+
],
42+
3643
// When scoping PHP files, there will be scenarios where some of the code being scoped indirectly references the
3744
// original namespace. These will include, for example, strings or string manipulations. PHP-Scoper has limited
3845
// support for prefixing such strings. To circumvent that, you can define patchers to manipulate the file to your

0 commit comments

Comments
 (0)