Skip to content

Commit c508dde

Browse files
authored
Add nowdoc support (#275)
Scopes the content of nowdoc elements provided they start off with a `<?php` tag. If the content is invalid PHP, then it will not scope the nowdoc contents.
1 parent 359688e commit c508dde

File tree

7 files changed

+305
-28
lines changed

7 files changed

+305
-28
lines changed

fixtures/set018-nikic-parser/expected-output

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,40 @@
11
array(
2-
0: Stmt_Function(
3-
byRef: false
4-
name: test
5-
params: array(
6-
0: Param(
7-
type: null
8-
byRef: false
9-
variadic: false
10-
name: foo
11-
default: null
2+
0: Stmt_Namespace(
3+
name: Name(
4+
parts: array(
5+
0: _Prefixed
126
)
137
)
14-
returnType: null
158
stmts: array(
16-
0: Expr_FuncCall(
17-
name: Name(
18-
parts: array(
19-
0: var_dump
9+
0: Stmt_Function(
10+
byRef: false
11+
name: test
12+
params: array(
13+
0: Param(
14+
type: null
15+
byRef: false
16+
variadic: false
17+
name: foo
18+
default: null
2019
)
2120
)
22-
args: array(
23-
0: Arg(
24-
value: Expr_Variable(
25-
name: foo
21+
returnType: null
22+
stmts: array(
23+
0: Expr_FuncCall(
24+
name: Name_FullyQualified(
25+
parts: array(
26+
0: var_dump
27+
)
28+
)
29+
args: array(
30+
0: Arg(
31+
value: Expr_Variable(
32+
name: foo
33+
)
34+
byRef: false
35+
unpack: false
36+
)
2637
)
27-
byRef: false
28-
unpack: false
2938
)
3039
)
3140
)

specs/nowdoc.php

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
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+
return [
16+
'meta' => [
17+
'title' => 'Nowdoc',
18+
// Default values. If not specified will be the one used
19+
'prefix' => 'Humbug',
20+
'whitelist' => [],
21+
'whitelist-global-constants' => false,
22+
'whitelist-global-classes' => false,
23+
'whitelist-global-functions' => false,
24+
'registered-classes' => [],
25+
'registered-functions' => [],
26+
],
27+
28+
'string' => <<<'PHP'
29+
<?php
30+
31+
$x = '
32+
<?php
33+
34+
use Acme\Foo;
35+
36+
';
37+
38+
----
39+
<?php
40+
41+
namespace Humbug;
42+
43+
$x = '
44+
<?php
45+
46+
use Acme\\Foo;
47+
48+
';
49+
50+
PHP
51+
,
52+
53+
'Nowdoc' => <<<'PHP'
54+
<?php
55+
56+
$x = <<<'PHP_NOWDOC'
57+
<?php
58+
59+
use Acme\Foo;
60+
61+
PHP_NOWDOC;
62+
63+
$y = <<<'PHP_NOWDOC'
64+
<?php
65+
66+
use Acme\Foo;
67+
PHP_NOWDOC;
68+
69+
----
70+
<?php
71+
72+
namespace Humbug;
73+
74+
$x = <<<'PHP_NOWDOC'
75+
<?php
76+
77+
namespace Humbug;
78+
79+
use Humbug\Acme\Foo;
80+
81+
PHP_NOWDOC;
82+
$y = <<<'PHP_NOWDOC'
83+
<?php
84+
85+
namespace Humbug;
86+
87+
use Humbug\Acme\Foo;
88+
PHP_NOWDOC
89+
;
90+
91+
PHP
92+
,
93+
94+
'Nowdoc with invalid PHP' => <<<'PHP'
95+
<?php
96+
97+
$x = <<<'PHP_NOWDOC'
98+
Not.php
99+
PHP_NOWDOC;
100+
101+
----
102+
<?php
103+
104+
namespace Humbug;
105+
106+
$x = <<<'PHP_NOWDOC'
107+
Not.php
108+
PHP_NOWDOC
109+
;
110+
111+
PHP
112+
,
113+
114+
'Partial PHP nowdoc' => <<<'PHP'
115+
<?php
116+
117+
$x = <<<'PHP_NOWDOC'
118+
use Acme\Foo;
119+
PHP_NOWDOC;
120+
121+
----
122+
<?php
123+
124+
namespace Humbug;
125+
126+
$x = <<<'PHP_NOWDOC'
127+
use Acme\Foo;
128+
PHP_NOWDOC
129+
;
130+
131+
PHP
132+
,
133+
134+
'Empty nowdoc' => <<<'PHP'
135+
<?php
136+
137+
$x = <<<'PHP_NOWDOC'
138+
PHP_NOWDOC;
139+
140+
----
141+
<?php
142+
143+
namespace Humbug;
144+
145+
$x = <<<'PHP_NOWDOC'
146+
PHP_NOWDOC
147+
;
148+
149+
PHP
150+
,
151+
152+
'Heredoc' => <<<'PHP'
153+
<?php
154+
155+
$x = <<<PHP_HEREDOC
156+
<?php
157+
158+
use Acme\Foo;
159+
160+
PHP_HEREDOC;
161+
162+
----
163+
<?php
164+
165+
namespace Humbug;
166+
167+
$x = <<<PHP_HEREDOC
168+
<?php
169+
170+
use Acme\\Foo;
171+
172+
PHP_HEREDOC
173+
;
174+
175+
PHP
176+
,
177+
];
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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\PhpParser\NodeVisitor;
16+
17+
use Humbug\PhpScoper\Scoper\PhpScoper;
18+
use Humbug\PhpScoper\Whitelist;
19+
use PhpParser\Error as PhpParserError;
20+
use PhpParser\Node;
21+
use PhpParser\Node\Scalar\String_;
22+
use PhpParser\NodeVisitorAbstract;
23+
use function ltrim;
24+
use function strpos;
25+
use function substr;
26+
27+
final class NewdocPrefixer extends NodeVisitorAbstract
28+
{
29+
private $scoper;
30+
private $prefix;
31+
private $whitelist;
32+
33+
public function __construct(PhpScoper $scoper, string $prefix, Whitelist $whitelist)
34+
{
35+
$this->scoper = $scoper;
36+
$this->prefix = $prefix;
37+
$this->whitelist = $whitelist;
38+
}
39+
40+
/**
41+
* @inheritdoc
42+
*/
43+
public function enterNode(Node $node): Node
44+
{
45+
if ($node instanceof String_ && $this->isPhpNowdoc($node)) {
46+
try {
47+
$lastChar = substr($node->value, -1);
48+
49+
$newValue = $this->scoper->scopePhp($node->value, $this->prefix, $this->whitelist);
50+
51+
if ("\n" !== $lastChar) {
52+
$newValue = substr($newValue, 0, -1);
53+
}
54+
55+
$node->value = $newValue;
56+
} catch (PhpParserError $error) {
57+
// Continue without scoping the heredoc which for some reasons contains invalid PHP code
58+
}
59+
}
60+
61+
return $node;
62+
}
63+
64+
private function isPhpNowdoc(String_ $node): bool
65+
{
66+
if (String_::KIND_NOWDOC !== $node->getAttribute('kind')) {
67+
return false;
68+
}
69+
70+
return 0 === strpos(
71+
substr(ltrim($node->value), 0, 5),
72+
'<?php'
73+
);
74+
}
75+
}

src/PhpParser/TraverserFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Humbug\PhpScoper\PhpParser\NodeVisitor\Collection\UseStmtCollection;
1919
use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver;
2020
use Humbug\PhpScoper\Reflector;
21+
use Humbug\PhpScoper\Scoper\PhpScoper;
2122
use Humbug\PhpScoper\Whitelist;
2223
use PhpParser\NodeTraverserInterface;
2324

@@ -33,7 +34,7 @@ public function __construct(Reflector $reflector)
3334
$this->reflector = $reflector;
3435
}
3536

36-
public function create(string $prefix, Whitelist $whitelist): NodeTraverserInterface
37+
public function create(PhpScoper $scoper, string $prefix, Whitelist $whitelist): NodeTraverserInterface
3738
{
3839
$traverser = new NodeTraverser($prefix);
3940

@@ -53,6 +54,7 @@ public function create(string $prefix, Whitelist $whitelist): NodeTraverserInter
5354
$traverser->addVisitor(new NodeVisitor\ClassIdentifierRecorder($prefix, $nameResolver, $whitelist));
5455
$traverser->addVisitor(new NodeVisitor\NameStmtPrefixer($prefix, $whitelist, $nameResolver, $this->reflector));
5556
$traverser->addVisitor(new NodeVisitor\StringScalarPrefixer($prefix, $whitelist, $this->reflector));
57+
$traverser->addVisitor(new NodeVisitor\NewdocPrefixer($scoper, $prefix, $whitelist));
5658

5759
$traverser->addVisitor(new NodeVisitor\ClassAliasStmtAppender($prefix, $whitelist, $nameResolver));
5860
$traverser->addVisitor(new NodeVisitor\ConstStmtReplacer($whitelist, $nameResolver));

src/Scoper/PhpScoper.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,14 @@ public function scope(string $filePath, string $contents, string $prefix, array
5252
return $this->decoratedScoper->scope($filePath, $contents, $prefix, $patchers, $whitelist);
5353
}
5454

55-
$statements = $this->parser->parse($contents);
55+
return $this->scopePhp($contents, $prefix, $whitelist);
56+
}
57+
58+
public function scopePhp(string $php, string $prefix, Whitelist $whitelist): string
59+
{
60+
$statements = $this->parser->parse($php);
5661

57-
$traverser = $this->traverserFactory->create($prefix, $whitelist);
62+
$traverser = $this->traverserFactory->create($this, $prefix, $whitelist);
5863

5964
$statements = $traverser->traverse($statements);
6065

tests/PhpParser/TraverserFactoryTest.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
namespace Humbug\PhpScoper\PhpParser;
1616

1717
use Humbug\PhpScoper\Reflector;
18+
use Humbug\PhpScoper\Scoper\FakeScoper;
19+
use Humbug\PhpScoper\Scoper\PhpScoper;
1820
use Humbug\PhpScoper\Whitelist;
1921
use PHPUnit\Framework\TestCase;
22+
use ReflectionClass;
2023
use Roave\BetterReflection\BetterReflection;
2124

2225
/**
@@ -37,8 +40,14 @@ public function test_creates_a_new_traverser_at_each_call(): void
3740

3841
$traverserFactory = new TraverserFactory($classReflector);
3942

40-
$firstTraverser = $traverserFactory->create($prefix, $whitelist);
41-
$secondTraverser = $traverserFactory->create($prefix, $whitelist);
43+
$phpScoper = new PhpScoper(
44+
new FakeParser(),
45+
new FakeScoper(),
46+
(new ReflectionClass(TraverserFactory::class))->newInstanceWithoutConstructor()
47+
);
48+
49+
$firstTraverser = $traverserFactory->create($phpScoper, $prefix, $whitelist);
50+
$secondTraverser = $traverserFactory->create($phpScoper, $prefix, $whitelist);
4251

4352
$this->assertNotSame($firstTraverser, $secondTraverser);
4453
}

0 commit comments

Comments
 (0)