3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
22 * Reads PHP code and returns the FQCN of every class defined within it.
24 class ClassCollector
{
27 * @var string Current namespace
29 protected $namespace = '';
32 * @var array List of FQCN detected in this pass
37 * @var array Token from token_get_all() that started an expect sequence
39 protected $startToken;
42 * @var array List of tokens that are members of the current expect sequence
47 * @var array Class alias with target/name fields
52 * @param string $code PHP code (including <?php) to detect class names from
53 * @return array List of FQCN detected within the tokens
55 public function getClasses( $code ) {
56 $this->namespace = '';
58 $this->startToken
= null;
62 foreach ( token_get_all( $code ) as $token ) {
63 if ( $this->startToken
=== null ) {
64 $this->tryBeginExpect( $token );
66 $this->tryEndExpect( $token );
70 return $this->classes
;
74 * Determine if $token begins the next expect sequence.
78 protected function tryBeginExpect( $token ) {
79 if ( is_string( $token ) ) {
82 // Note: When changing class name discovery logic,
83 // AutoLoaderStructureTest.php may also need to be updated.
84 switch ( $token[0] ) {
91 $this->startToken
= $token;
94 if ( $token[1] === 'class_alias' ) {
95 $this->startToken
= $token;
102 * Accepts the next token in an expect sequence
104 * @param array $token
106 protected function tryEndExpect( $token ) {
107 switch ( $this->startToken
[0] ) {
109 // Skip over T_CLASS after T_DOUBLE_COLON because this is something like
110 // "self::static" which accesses the class name. It doens't define a new class.
111 $this->startToken
= null;
114 // Skip over T_CLASS after T_NEW because this is a PHP 7 anonymous class.
115 if ( !is_array( $token ) ||
$token[0] !== T_WHITESPACE
) {
116 $this->startToken
= null;
120 if ( $token === ';' ||
$token === '{' ) {
121 $this->namespace = $this->implodeTokens() . '\\';
123 $this->tokens
[] = $token;
128 if ( $this->alias
!== null ) {
129 // Flow 1 - Two string literals:
130 // - T_STRING class_alias
132 // - T_CONSTANT_ENCAPSED_STRING 'TargetClass'
135 // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
137 // Flow 2 - Use of ::class syntax for first parameter
138 // - T_STRING class_alias
140 // - T_STRING TargetClass
141 // - T_DOUBLE_COLON ::
145 // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
147 if ( $token === '(' ) {
148 // Start of a function call to class_alias()
149 $this->alias
= [ 'target' => false, 'name' => false ];
150 } elseif ( $token === ',' ) {
151 // Record that we're past the first parameter
152 if ( $this->alias
['target'] === false ) {
153 $this->alias
['target'] = true;
155 } elseif ( is_array( $token ) && $token[0] === T_CONSTANT_ENCAPSED_STRING
) {
156 if ( $this->alias
['target'] === true ) {
157 // We already saw a first argument, this must be the second.
158 // Strip quotes from the string literal.
159 $this->alias
['name'] = substr( $token[1], 1, -1 );
161 } elseif ( $token === ')' ) {
162 // End of function call
163 $this->classes
[] = $this->alias
['name'];
165 $this->startToken
= null;
166 } elseif ( !is_array( $token ) ||
(
167 $token[0] !== T_STRING
&&
168 $token[0] !== T_DOUBLE_COLON
&&
169 $token[0] !== T_CLASS
&&
170 $token[0] !== T_WHITESPACE
172 // Ignore this call to class_alias() - compat/Timestamp.php
174 $this->startToken
= null;
182 $this->tokens
[] = $token;
183 if ( is_array( $token ) && $token[0] === T_STRING
) {
184 $this->classes
[] = $this->namespace . $this->implodeTokens();
190 * Returns the string representation of the tokens within the
191 * current expect sequence and resets the sequence.
195 protected function implodeTokens() {
197 foreach ( $this->tokens
as $token ) {
198 $content[] = is_string( $token ) ?
$token : $token[1];
202 $this->startToken
= null;
204 return trim( implode( '', $content ), " \n\t" );