Skip to content

Commit

Permalink
Add files whitelist feature
Browse files Browse the repository at this point in the history
Closes humbug#196
  • Loading branch information
theofidry committed Oct 3, 2018
1 parent f595614 commit 9f2ddc2
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 8 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ return [
'prefix' => null, // string|null
'finders' => [], // Finder[]
'patchers' => [], // callable[]
'whitelisted-files' => [], // string[]
'whitelist' => [], // string[]
'whitelist-global-constants' => true, // bool
'whitelist-global-classes' => true, // bool
Expand Down Expand Up @@ -262,6 +263,12 @@ return [
```


### Whitelisted files

For the files listed in `whitelisted-files`, their content will be left
untouched during the scoping process.


### Whitelist

PHP-Scoper's goal is to make sure that all code for a project lies in a
Expand Down
82 changes: 74 additions & 8 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
namespace Humbug\PhpScoper;

use Closure;
use const DIRECTORY_SEPARATOR;
use function dirname;
use function gettype;
use InvalidArgumentException;
use function is_array;
use function is_string;
use Iterator;
use function realpath;
use RuntimeException;
use SplFileInfo;
use Symfony\Component\Finder\Finder;
Expand All @@ -28,7 +34,8 @@
*/
class Configuration
{
private const PREFIX = 'prefix';
private const PREFIX_KEYWORD = 'prefix';
private const WHITELISTED_FILES_KEYWORD = 'files-whitelist';
private const FINDER_KEYWORD = 'finders';
private const PATCHERS_KEYWORD = 'patchers';
private const WHITELIST_KEYWORD = 'whitelist';
Expand All @@ -37,10 +44,13 @@ class Configuration
private const WHITELIST_GLOBAL_FUNCTIONS_KEYWORD = 'whitelist-global-functions';

private const KEYWORDS = [
self::PREFIX,
self::PREFIX_KEYWORD,
self::WHITELISTED_FILES_KEYWORD,
self::FINDER_KEYWORD,
self::PATCHERS_KEYWORD,
self::WHITELIST_KEYWORD,
self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD,
self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
];

Expand All @@ -49,6 +59,7 @@ class Configuration
private $filesWithContents;
private $patchers;
private $whitelist;
private $whitelistedFiles;

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

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

$whitelistedFiles = null === $path ? [] : self::retrieveWhitelistedFiles(dirname($path), $config);

$patchers = self::retrievePatchers($config);
$whitelist = self::retrieveWhitelist($config);

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

return new self($path, $prefix, $filesWithContents, $patchers, $whitelist);
return new self($path, $prefix, $filesWithContents, $patchers, $whitelist, $whitelistedFiles);
}

/**
Expand All @@ -97,19 +110,22 @@ public static function load(string $path = null, array $paths = []): self
* @param Closure $globalNamespaceWhitelisters Closure taking a class name from the global namespace as an argument and
* returning a boolean which if `true` means the class should be scoped
* (i.e. is ignored) or scoped otherwise.
* @param string[] $whitelistedFiles List of absolute paths of files to completely ignore
*/
private function __construct(
?string $path,
?string $prefix,
array $filesWithContents,
array $patchers,
Whitelist $whitelist
Whitelist $whitelist,
array $whitelistedFiles
) {
$this->path = $path;
$this->prefix = $prefix;
$this->filesWithContents = $filesWithContents;
$this->patchers = $patchers;
$this->whitelist = $whitelist;
$this->whitelistedFiles = $whitelistedFiles;
}

public function withPaths(array $paths): self
Expand All @@ -127,20 +143,22 @@ public function withPaths(array $paths): self
$this->prefix,
array_merge($this->filesWithContents, $filesWithContents),
$this->patchers,
$this->whitelist
$this->whitelist,
$this->whitelistedFiles
);
}

public function withPrefix(?string $prefix): self
{
$prefix = self::retrievePrefix([self::PREFIX => $prefix]);
$prefix = self::retrievePrefix([self::PREFIX_KEYWORD => $prefix]);

return new self(
$this->path,
$prefix,
$this->filesWithContents,
$this->patchers,
$this->whitelist
$this->whitelist,
$this->whitelistedFiles
);
}

Expand Down Expand Up @@ -172,6 +190,14 @@ public function getWhitelist(): Whitelist
return $this->whitelist;
}

/**
* @return string[]
*/
public function getWhitelistedFiles(): array
{
return $this->whitelistedFiles;
}

private static function validateConfigKeys(array $config): void
{
array_map(
Expand Down Expand Up @@ -200,7 +226,7 @@ private static function validateConfigKey(string $key): void
*/
private static function retrievePrefix(array $config): ?string
{
$prefix = array_key_exists(self::PREFIX, $config) ? $config[self::PREFIX] : null;
$prefix = array_key_exists(self::PREFIX_KEYWORD, $config) ? $config[self::PREFIX_KEYWORD] : null;

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

/**
* @return string[] Absolute paths
*/
private static function retrieveWhitelistedFiles(string $dirPath, array $config): array
{
if (false === array_key_exists(self::WHITELISTED_FILES_KEYWORD, $config)) {
return [];
}

$whitelistedFiles = $config[self::WHITELIST_KEYWORD];

if (false === is_array($whitelistedFiles)) {
throw new InvalidArgumentException(
sprintf(
'Expected whitelisted files to be an array of strings, found "%s" instead.',
gettype($whitelistedFiles)
)
);
}

foreach ($whitelistedFiles as $index => $file) {
if (is_string($file)) {
throw new InvalidArgumentException(
sprintf(
'Expected whitelisted files to be an array of string, the "%d" element is not.',
$index
)
);
}

if ('' !== $file && DIRECTORY_SEPARATOR !== $file[0]) {
$file = $dirPath.DIRECTORY_SEPARATOR.$file;
}

$whitelistedFiles[$index] = realpath($file);
}

return array_filter($whitelistedFiles);
}

private static function retrieveFinders(array $config): array
{
if (false === array_key_exists(self::FINDER_KEYWORD, $config)) {
Expand Down
1 change: 1 addition & 0 deletions src/Console/ApplicationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Humbug\PhpScoper\Scoper;
use Humbug\PhpScoper\Scoper\Composer\InstalledPackagesScoper;
use Humbug\PhpScoper\Scoper\Composer\JsonFileScoper;
use Humbug\PhpScoper\Scoper\FileWhitelistScoper;
use Humbug\PhpScoper\Scoper\NullScoper;
use Humbug\PhpScoper\Scoper\PatchScoper;
use Humbug\PhpScoper\Scoper\PhpScoper;
Expand Down
9 changes: 9 additions & 0 deletions src/Console/Command/AddPrefixCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@

namespace Humbug\PhpScoper\Console\Command;

use function count;
use Humbug\PhpScoper\Autoload\ScoperAutoloadGenerator;
use Humbug\PhpScoper\Configuration;
use Humbug\PhpScoper\Logger\ConsoleLogger;
use Humbug\PhpScoper\Scoper;
use Humbug\PhpScoper\Scoper\FileWhitelistScoper;
use Humbug\PhpScoper\Throwable\Exception\ParsingException;
use Humbug\PhpScoper\Whitelist;
use Symfony\Component\Console\Exception\RuntimeException;
Expand Down Expand Up @@ -133,6 +135,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$config = $this->retrieveConfig($input, $output, $io);
$output = $input->getOption(self::OUTPUT_DIR_OPT);

if ([] !== $config->getWhitelistedFiles()) {
$this->scoper = new FileWhitelistScoper(
$this->scoper,
...$config->getWhitelistedFiles()
);
}

$logger = new ConsoleLogger(
$this->getApplication(),
$io
Expand Down
44 changes: 44 additions & 0 deletions src/Scoper/FileWhitelistScoper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/*
* This file is part of the humbug/php-scoper package.
*
* Copyright (c) 2017 Théo FIDRY <[email protected]>,
* Pádraic Brady <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Humbug\PhpScoper\Scoper;

use function array_flip;
use function array_key_exists;
use Humbug\PhpScoper\Scoper;
use Humbug\PhpScoper\Whitelist;

final class FileWhitelistScoper implements Scoper
{
private $decoratedScoper;
private $filePaths;

public function __construct(Scoper $decoratedScoper, string ...$filePaths)
{
$this->decoratedScoper = $decoratedScoper;
$this->filePaths = array_flip($filePaths);
}

/**
* @inheritdoc
*/
public function scope(string $filePath, string $contents, string $prefix, array $patchers, Whitelist $whitelist): string
{
if (array_key_exists($filePath, $this->filePaths)) {
return $contents;
}

return $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist);
}
}
7 changes: 7 additions & 0 deletions src/scoper.inc.php.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ return [
]),
],

// Whitelists a list of files. Unlike the other whitelist related features, this one is about completely leaving
// a file untouched.
// Paths are relative to the configuration file unless if they are already absolute
'files-whitelist' => [
'src/a-whitelisted-file.php',
],

// When scoping PHP files, there will be scenarios where some of the code being scoped indirectly references the
// original namespace. These will include, for example, strings or string manipulations. PHP-Scoper has limited
// support for prefixing such strings. To circumvent that, you can define patchers to manipulate the file to your
Expand Down
81 changes: 81 additions & 0 deletions tests/Scoper/FileWhitelistScoperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

/*
* This file is part of the humbug/php-scoper package.
*
* Copyright (c) 2017 Théo FIDRY <[email protected]>,
* Pádraic Brady <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Humbug\PhpScoper\Scoper;

use Humbug\PhpScoper\Scoper;
use Humbug\PhpScoper\Whitelist;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;

/**
* @covers \Humbug\PhpScoper\Scoper\FileWhitelistScoper
*/
class FileWhitelistScoperTest extends TestCase
{
/**
* @var Scoper|ObjectProphecy
*/
private $decoratedScoperProphecy;

/**
* @var Scoper
*/
private $decoratedScoper;

/**
* @inheritdoc
*/
public function setUp()
{
$this->decoratedScoperProphecy = $this->prophesize(Scoper::class);
$this->decoratedScoper = $this->decoratedScoperProphecy->reveal();
}

public function test_is_a_Scoper()
{
$this->assertTrue(is_a(FileWhitelistScoper::class, Scoper::class, true));
}

public function test_it_scopes_the_file_contents_with_the_decorated_scoper_if_file_not_whitelisted_and_the_contents_unchanged_when_is_whitelisted()
{
$whitelistedFilePath = '/path/to/whitelist-file.php';
$notWhitelistedFilePath = '/path/to/not-file.php';
$contents = 'Original file content';
$prefix = 'Humbug';
$patchers = [];
$whitelist = Whitelist::create(true, true, true, 'Foo');

$this->decoratedScoperProphecy
->scope($notWhitelistedFilePath, $contents, $prefix, $patchers, $whitelist)
->willReturn($scopedContents = 'Decorated scoper contents')
;

$scoper = new FileWhitelistScoper($this->decoratedScoper, $whitelistedFilePath);

$this->assertSame(
$scopedContents,
$scoper->scope($notWhitelistedFilePath, $contents, $prefix, $patchers, $whitelist)
);

$this->assertSame(
$contents,
$scoper->scope($whitelistedFilePath, $contents, $prefix, $patchers, $whitelist)
);

$this->decoratedScoperProphecy->scope(Argument::cetera())->shouldHaveBeenCalledTimes(1);
}
}

0 comments on commit 9f2ddc2

Please sign in to comment.