Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to whitelist constants #214

Merged
merged 3 commits into from
Jun 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ potentially very difficult to debug due to dissimilar or unsupported package ver
- [Finders and paths](#finders-and-paths)
- [Patchers](#patchers)
- [Whitelist][whitelist]
- [Class Whitelisting](#class-whitelisting)
- [Class & Constant Whitelisting](#class-constant-whitelisting)
- [Namespace Whitelisting](#namespace-whitelisting)
- [Building A Scoped PHAR](#building-a-scoped-phar)
- [With Box](#with-box)
Expand Down Expand Up @@ -261,9 +261,9 @@ a PHPUnit PHAR with isolated code, you still want the PHAR to be able to
understand the `PHPUnit\Framework\TestCase` class.


### Class whitelisting
### Class & Constant whitelisting

You can whitelist classes and interfaces like so:
You can whitelist classes, interfaces and constants like so like so:

```php
<?php declare(strict_types=1);
Expand All @@ -273,15 +273,26 @@ You can whitelist classes and interfaces like so:
return [
'whitelist' => [
'PHPUnit\Framework\TestCase',
'PHPUNIT_VERSION',
],
];
```

Note that only classes are whitelisted, this does not affect constants,
functions or traits. This whitelisting will actually not prevent the
scoping to operate, i.e. the class or interface will still be prefixed,
but a `class_alias()` statement will be registered pointing the prefixed
class to the non-prefixed one.
This will _not_ work on traits or functions.

The class aliasing mechanism is done like follows:
- Prefix the class or interface as usual
- Append a `class_alias()` statement at the end of the class/interface declaration to link the prefixed symbol to the
non prefixed one
- Append a `class_exists()` statement right after the autoloader is registered to trigger the loading of the method
which will ensure the `class_alias()` statement is executed

It is done this way to ensure prefixed and whitelisted classes can co-exist together without breaking the autoloading.

The constant aliasing mechanism is done by transforming the constant declaration into a `define()` statement when this
is not already the case. Note that there is a difference here since `define()` defines a constant at runtime whereas
`const` defines it at compile time. You have a more details post regarding the differences
[here](https://stackoverflow.com/a/3193704/3902761)


### Namespace whitelisting
Expand Down
164 changes: 164 additions & 0 deletions specs/const/const-declaration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?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.
*/

return [
'meta' => [
'title' => 'Global constant usage in the global scope',
// Default values. If not specified will be the one used
'prefix' => 'Humbug',
'whitelist' => [],
],

'Constants declaration in the global namespace' => [
'payload' => <<<'PHP'
<?php

const FOO_CONST = foo();
define('BAR_CONST', foo());
define(FOO_CONST, foo());
define(\FOO_CONST, foo());
----
<?php

namespace Humbug;

const FOO_CONST = \Humbug\foo();
\define('BAR_CONST', \Humbug\foo());
\define(\Humbug\FOO_CONST, \Humbug\foo());
\define(\Humbug\FOO_CONST, \Humbug\foo());

PHP
],

'Constants declaration in the global namespace which is whitelisted' => [
'whitelist' => ['*'],
'payload' => <<<'PHP'
<?php

const FOO_CONST = foo();
define('BAR_CONST', foo());
define(FOO_CONST, foo());
define(\FOO_CONST, foo());
----
<?php

namespace {
const FOO_CONST = \foo();
\define('BAR_CONST', \foo());
\define(\FOO_CONST, \foo());
\define(\FOO_CONST, \foo());
}

PHP
],

'Whitelisted constants declaration in the global namespace' => [
'whitelist' => ['FOO_CONST', 'BAR_CONST'],
'payload' => <<<'PHP'
<?php

const FOO_CONST = foo();
define('BAR_CONST', foo());
define(FOO_CONST, foo());
define(\FOO_CONST, foo());
----
<?php

namespace Humbug;

\define('FOO_CONST', \Humbug\foo());
\define('BAR_CONST', \Humbug\foo());
\define(\FOO_CONST, \Humbug\foo());
\define(\FOO_CONST, \Humbug\foo());

PHP
],

'Constants declaration in a namespace' => [
'payload' => <<<'PHP'
<?php

namespace Acme;

const FOO_CONST = foo();
define('BAR_CONST', foo());
define(FOO_CONST, foo());
define(\FOO_CONST, foo());
define(\Acme\FOO_CONST, foo());
----
<?php

namespace Humbug\Acme;

const FOO_CONST = foo();
\define('BAR_CONST', foo());
\define(FOO_CONST, foo());
\define(\Humbug\FOO_CONST, foo());
\define(\Humbug\Acme\FOO_CONST, foo());

PHP
],

'Constants declaration in a whitelisted namespace' => [
'whitelist' => ['Acme\*'],
'payload' => <<<'PHP'
<?php

namespace Acme;

const FOO_CONST = foo();
define('BAR_CONST', foo());
define(FOO_CONST, foo());
define(\FOO_CONST, foo());
define(\Acme\FOO_CONST, foo());
----
<?php

namespace Acme;

const FOO_CONST = foo();
\define('BAR_CONST', foo());
\define(FOO_CONST, foo());
\define(\Humbug\FOO_CONST, foo());
\define(\Acme\FOO_CONST, foo());

PHP
],

'Whitelisted constants declaration in a namespace' => [
'whitelist' => ['Acme\FOO_CONST'],
'payload' => <<<'PHP'
<?php

namespace Acme;

const FOO_CONST = foo();
define('BAR_CONST', foo());
define(FOO_CONST, foo());
define(\FOO_CONST, foo());
define(\Acme\FOO_CONST, foo());
----
<?php

namespace Humbug\Acme;

\define('Acme\\FOO_CONST', foo());
\define('BAR_CONST', foo());
\define(FOO_CONST, foo());
\define(\Humbug\FOO_CONST, foo());
\define(\Acme\FOO_CONST, foo());

PHP
],
];
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
'whitelist' => [],
],

// As it is extremely rare to use a `use const` statement for a built-in constant from the
// global scope, we can relatively safely assume it is a user-land declared constant which should
// be prefixed.

[
'spec' => <<<'SPEC'
Constant call imported with an aliased use statement:
Expand All @@ -46,6 +42,31 @@
use const Humbug\DUMMY_CONST as FOO;
\Humbug\DUMMY_CONST;

PHP
],

[
'spec' => <<<'SPEC'
Whitelisted constant call imported with an aliased use statement:
- add prefixed namespace
- transforms the call into a FQ call
SPEC
,
'whitelist' => ['DUMMY_CONST'],
'payload' => <<<'PHP'
<?php

use const DUMMY_CONST as FOO;

FOO;
----
<?php

namespace Humbug;

use const DUMMY_CONST as FOO;
\DUMMY_CONST;

PHP
],

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
'whitelist' => [],
],

// As it is extremely rare to use a `use const` statement for a built-in constant from the
// global scope, we can relatively safely assume it is a user-land declared constant which should
// be prefixed.

[
'spec' => <<<'SPEC'
Constant call imported with a use statement:
Expand All @@ -46,6 +42,31 @@
use const Humbug\DUMMY_CONST;
\Humbug\DUMMY_CONST;

PHP
],

[
'spec' => <<<'SPEC'
Whitelisted constant call imported with a use statement:
- add prefixed namespace
- transforms the call into a FQ call
SPEC
,
'whitelist' => ['DUMMY_CONST'],
'payload' => <<<'PHP'
<?php

use const DUMMY_CONST;

DUMMY_CONST;
----
<?php

namespace Humbug;

use const DUMMY_CONST;
\DUMMY_CONST;

PHP
],

Expand Down
22 changes: 22 additions & 0 deletions specs/const/global-scope-global.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@

\Humbug\DUMMY_CONST;

PHP
],

[
'spec' => <<<'SPEC'
Whitelisted constant call in the global namespace:
- add prefixed namespace
- transforms the call into a FQ call
SPEC
,
'whitelist' => ['DUMMY_CONST'],
'payload' => <<<'PHP'
<?php

DUMMY_CONST;
----
<?php

namespace Humbug;

\DUMMY_CONST;

PHP
],

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@ class Foo

[
'spec' => <<<'SPEC'
Constant call on an imported single-level namespace
Whitelisted onstant call on an imported single-level namespace
- do not prefix the use statement (see tests related to single-level classes)
- prefix the constant call: the whitelist only works on classes
- transform the call into a FQ call
SPEC
,
Expand Down Expand Up @@ -140,11 +139,11 @@ class Foo
}
namespace Humbug\Foo;

const DUMMY_CONST = '';
\define('Foo\\DUMMY_CONST', '');
namespace Humbug;

use Humbug\Foo as A;
\Humbug\Foo\DUMMY_CONST;
\Foo\DUMMY_CONST;

PHP
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

[
'spec' => <<<'SPEC'
Constant call on an imported single-level namespace
Constant call on an imported single-level namespace:
- add prefixed namespace
- do not prefix the use statement (see tests related to single-level classes)
- prefix the constant call
- transform the call into a FQ call
Expand Down Expand Up @@ -94,7 +95,7 @@ class Foo

[
'spec' => <<<'SPEC'
Constant call on an imported single-level namespace
Whitelisted constant call on an imported single-level namespace
- do not prefix the use statement (see tests related to single-level classes)
- prefix the constant call: the whitelist only works on classes
- transform the call into a FQ call
Expand Down Expand Up @@ -127,11 +128,11 @@ class Foo
}
namespace Humbug\Foo;

const DUMMY_CONST = '';
\define('Foo\\DUMMY_CONST', '');
namespace Humbug;

use Humbug\Foo;
\Humbug\Foo\DUMMY_CONST;
\Foo\DUMMY_CONST;

PHP
],
Expand Down
Loading