1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This program is free software. It comes without any warranty, to |
4
|
|
|
* the extent permitted by applicable law. You can redistribute it |
5
|
|
|
* and/or modify it under the terms of the Do What The Fuck You Want |
6
|
|
|
* To Public License, Version 2, as published by Sam Hocevar. See |
7
|
|
|
* http://www.wtfpl.net/ for more details. |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
declare(strict_types = 1); |
11
|
|
|
|
12
|
|
|
namespace hanneskod\classtools\Transformer; |
13
|
|
|
|
14
|
|
|
use hanneskod\classtools\Exception\RuntimeException; |
15
|
|
|
use hanneskod\classtools\Exception\ReaderException; |
16
|
|
|
use hanneskod\classtools\Name; |
17
|
|
|
use PhpParser\Lexer\Emulative; |
18
|
|
|
use PhpParser\Node\Stmt\Class_; |
19
|
|
|
use PhpParser\Node\Stmt\Interface_; |
20
|
|
|
use PhpParser\Node\Stmt\Namespace_; |
21
|
|
|
use PhpParser\Node\Stmt\Trait_; |
22
|
|
|
use PhpParser\Node\Stmt\Use_; |
23
|
|
|
use PhpParser\Parser; |
24
|
|
|
use PhpParser\ParserFactory; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Read classes, interfaces and traits from php snippets |
28
|
|
|
* |
29
|
|
|
* @author Hannes Forsgård <[email protected]> |
30
|
|
|
*/ |
31
|
|
|
class Reader |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* @var Namespace_[] Collection of definitions in snippet |
35
|
|
|
*/ |
36
|
|
|
private $defs = []; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var string[] Case sensitive definition names |
40
|
|
|
*/ |
41
|
|
|
private $names = []; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var \PhpParser\Node[] The global statement object |
45
|
|
|
*/ |
46
|
|
|
private $global; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Optionally inject parser |
50
|
|
|
* |
51
|
|
|
* @throws ReaderException If snippet contains a syntax error |
52
|
|
|
*/ |
53
|
|
|
public function __construct(string $snippet, Parser $parser = null) |
54
|
|
|
{ |
55
|
|
|
if (is_null($parser)) { |
56
|
|
|
$parserFactory = new ParserFactory(); |
57
|
|
|
$parser = $parserFactory->create(ParserFactory::PREFER_PHP5); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
try { |
61
|
|
|
$this->global = $parser->parse($snippet); |
|
|
|
|
62
|
|
|
} catch (\PhpParser\Error $exception) { |
63
|
|
|
throw new ReaderException($exception->getRawMessage() . ' on line ' . $exception->getStartLine()); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
$this->findDefinitions($this->global, new Name('')); |
|
|
|
|
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Find class, interface and trait definitions in statemnts |
71
|
|
|
*/ |
72
|
|
|
private function findDefinitions(array $stmts, Name $namespace): void |
73
|
|
|
{ |
74
|
|
|
$useStmts = []; |
75
|
|
|
|
76
|
|
|
foreach ($stmts as $stmt) { |
77
|
|
|
// Restart if namespace statement is found |
78
|
|
|
if ($stmt instanceof Namespace_) { |
79
|
|
|
$this->findDefinitions($stmt->stmts, new Name((string)$stmt->name)); |
80
|
|
|
|
81
|
|
|
// Save use statement |
82
|
|
|
} elseif ($stmt instanceof Use_) { |
83
|
|
|
$useStmts[] = $stmt; |
84
|
|
|
|
85
|
|
|
// Save classes, interfaces and traits |
86
|
|
|
} elseif ($stmt instanceof Class_ or $stmt instanceof Interface_ or $stmt instanceof Trait_) { |
87
|
|
|
$defName = new Name("{$namespace}\\{$stmt->name}"); |
88
|
|
|
$this->names[$defName->keyize()] = $defName->normalize(); |
89
|
|
|
$this->defs[$defName->keyize()] = new Namespace_( |
90
|
|
|
$namespace->normalize() ? $namespace->createNode() : null, |
91
|
|
|
$useStmts |
92
|
|
|
); |
93
|
|
|
$this->defs[$defName->keyize()]->stmts[] = $stmt; |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Get names of definitions in snippet |
100
|
|
|
* |
101
|
|
|
* @return string[] |
102
|
|
|
*/ |
103
|
|
|
public function getDefinitionNames(): array |
104
|
|
|
{ |
105
|
|
|
return array_values($this->names); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Check if snippet contains definition |
110
|
|
|
*/ |
111
|
|
|
public function hasDefinition(string $name): bool |
112
|
|
|
{ |
113
|
|
|
return isset($this->defs[(new Name($name))->keyize()]); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Get pars tree for class/interface/trait |
118
|
|
|
* |
119
|
|
|
* @return Namespace_[] |
120
|
|
|
* @throws RuntimeException If $name does not exist |
121
|
|
|
*/ |
122
|
|
|
public function read(string $name): array |
123
|
|
|
{ |
124
|
|
|
if (!$this->hasDefinition($name)) { |
125
|
|
|
throw new RuntimeException("Unable to read <$name>, not found."); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
return [$this->defs[(new Name($name))->keyize()]]; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Get parse tree for the complete snippet |
133
|
|
|
* |
134
|
|
|
* @return \PhpParser\Node[] |
135
|
|
|
*/ |
136
|
|
|
public function readAll(): array |
137
|
|
|
{ |
138
|
|
|
return $this->global; |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.
To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.
The function can be called with either null or an array for the parameter
$needle
but will only accept an array as$haystack
.