AbstractParser   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 4

Importance

Changes 0
Metric Value
wmc 32
lcom 0
cbo 4
dl 0
loc 263
rs 9.84
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 34 5
A usesKeyword() 0 10 2
A getStartLine() 0 15 4
A getEndLine() 0 20 4
B hasSignatureToken() 0 34 9
B getDocBlock() 0 35 8
1
<?php
2
3
/**
4
 * \AppserverIo\Doppelgaenger\Parser\AbstractParser
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Bernhard Wick <[email protected]>
15
 * @copyright 2015 TechDivision GmbH - <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/doppelgaenger
18
 * @link      http://www.appserver.io/
19
 */
20
21
namespace AppserverIo\Doppelgaenger\Parser;
22
23
use AppserverIo\Doppelgaenger\Config;
24
use AppserverIo\Doppelgaenger\Interfaces\ParserInterface;
25
use AppserverIo\Doppelgaenger\StructureMap;
26
use AppserverIo\Doppelgaenger\Entities\Definitions\StructureDefinitionHierarchy;
27
use AppserverIo\Doppelgaenger\Exceptions\ParserException;
28
use AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface;
29
30
/**
31
 * The abstract class AbstractParser which provides a basic implementation other parsers can inherit from
32
 *
33
 * @author    Bernhard Wick <[email protected]>
34
 * @copyright 2015 TechDivision GmbH - <[email protected]>
35
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
36
 * @link      https://github.com/appserver-io/doppelgaenger
37
 * @link      http://www.appserver.io/
38
 */
39
abstract class AbstractParser implements ParserInterface
40
{
41
42
    /**
43
     * The aspect of the configuration we need
44
     *
45
     * @var \AppserverIo\Doppelgaenger\Config $config
46
     */
47
    protected $config;
48
49
    /**
50
     * The path of the file we want to parse
51
     *
52
     * @var string $file
53
     */
54
    protected $file;
55
56
    /**
57
     * The token array representing the whole file
58
     *
59
     * @var array $tokens
60
     */
61
    protected $tokens = array();
62
63
    /**
64
     * The count of our main token array, so we do not have to calculate it over and over again
65
     *
66
     * @var integer $tokenCount
67
     */
68
    protected $tokenCount;
69
70
    /**
71
     * The current definition we are working on.
72
     * This should be filled during parsing and should be passed down to whatever parser we need so we know about
73
     * the current "parent" definition parts.
74
     *
75
     * @var \AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface $currentDefinition
76
     */
77
    protected $currentDefinition;
78
79
    /**
80
     * The list of structures (within this hierarchy) which we already parsed.
81
     *
82
     * @var \AppserverIo\Doppelgaenger\Entities\Definitions\StructureDefinitionHierarchy $structureDefinitionHierarchy
83
     */
84
    protected $structureDefinitionHierarchy;
85
86
    /**
87
     * Our structure map instance
88
     *
89
     * @var \AppserverIo\Doppelgaenger\StructureMap $structureMap
90
     */
91
    protected $structureMap;
92
93
    /**
94
     * Default constructor
95
     *
96
     * @param string                                       $file                         The path of the file we want to parse
97
     * @param \AppserverIo\Doppelgaenger\Config            $config                       Configuration
98
     * @param StructureDefinitionHierarchy                 $structureDefinitionHierarchy List of already parsed structures
99
     * @param \AppserverIo\Doppelgaenger\StructureMap|null $structureMap                 Our structure map instance
100
     * @param StructureDefinitionInterface|null            $currentDefinition            The current definition we are working on
101
     * @param array                                        $tokens                       The array of tokens taken from the file
102
     *
103
     * @throws \AppserverIo\Doppelgaenger\Exceptions\ParserException
104
     */
105
    public function __construct(
106
        $file,
107
        Config $config,
108
        StructureDefinitionHierarchy $structureDefinitionHierarchy = null,
109
        StructureMap $structureMap = null,
110
        StructureDefinitionInterface $currentDefinition = null,
111
        array $tokens = array()
112
    ) {
113
        $this->config = $config;
114
115
        if (empty($tokens)) {
116
            // Check if we can use the file
117
            if (!is_readable($file)) {
118
                throw new ParserException(sprintf('Could not read input file %s', $file));
119
            }
120
121
            // Get all the tokens and count them
122
            $this->tokens = token_get_all(file_get_contents($file));
123
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
124
        } else {
125
            $this->tokens = $tokens;
126
        }
127
128
        // We need the file saved
129
        $this->file = $file;
130
131
        // We also need the token count
132
        $this->tokenCount = count($this->tokens);
133
134
        $this->currentDefinition = $currentDefinition;
135
136
        $this->structureMap = is_null($structureMap) ? new StructureMap($config->getValue('autoloader/dirs'), $config->getValue('enforcement/dirs'), $config) : $structureMap;
137
        $this->structureDefinitionHierarchy = is_null($structureDefinitionHierarchy) ? new StructureDefinitionHierarchy() : $structureDefinitionHierarchy;
138
    }
139
140
    /**
141
     * Does a certain block of code contain a certain keyword
142
     *
143
     * @param string $docBlock The code block to search in
144
     * @param string $keyword  The keyword to search for
145
     *
146
     * @return boolean
147
     */
148
    protected function usesKeyword(
149
        $docBlock,
150
        $keyword
151
    ) {
152
        if (strpos($docBlock, $keyword) === false) {
153
            return false;
154
        } else {
155
            return true;
156
        }
157
    }
158
159
    /**
160
     * Get the starting line of the structure, FALSE if unknown
161
     *
162
     * @param array $tokens The token array
163
     *
164
     * @return integer|boolean
165
     */
166
    protected function getStartLine($tokens)
167
    {
168
        // Check the tokens
169
        $targetToken = $this->getToken();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class AppserverIo\Doppelgaenger\Parser\AbstractParser as the method getToken() does only exist in the following sub-classes of AppserverIo\Doppelgaenger\Parser\AbstractParser: AppserverIo\Doppelgaenge...AbstractStructureParser, AppserverIo\Doppelgaenger\Parser\AspectParser, AppserverIo\Doppelgaenger\Parser\ClassParser, AppserverIo\Doppelgaenger\Parser\FunctionParser, AppserverIo\Doppelgaenger\Parser\InterfaceParser, AppserverIo\Doppelgaenger\Parser\TraitParser. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
170
        $tokenCount = count($tokens);
171
        for ($i = 0; $i < $tokenCount; $i++) {
172
            // If we got the target token indicating the structure start
173
            if ($tokens[$i][0] === $targetToken && $tokens[$i - 1][0] !== T_PAAMAYIM_NEKUDOTAYIM) {
174
                return $tokens[$i][2];
175
            }
176
        }
177
178
        // Return that we found nothing
179
        return false;
180
    }
181
182
    /**
183
     * Get the ending line of the structure, FALSE if unknown
184
     *
185
     * @param array $tokens The token array
186
     *
187
     * @return integer|boolean
188
     */
189
    protected function getEndLine($tokens)
190
    {
191
        // Check the tokens for a line number
192
        $lastIndex = (count($tokens) - 1);
193
        for ($i = $lastIndex; $i >= 0; $i--) {
194
            // If we got a token we know about the line number of the last token
195
            if (is_array($tokens[$i])) {
196
                // we found something already
197
                $endLine = $tokens[$i][2];
198
                // might be a linebreak as well
199
                if ($tokens[$i][0] === T_WHITESPACE) {
200
                    $endLine += substr_count($tokens[$i][1], "\n");
201
                }
202
                return $endLine;
203
            }
204
        }
205
206
        // Return that we found nothing
207
        return false;
208
    }
209
210
    /**
211
     * Will search for a certain token in a certain entity.
212
     *
213
     * This method will search the signature of either a class or a function for a certain token e.g. final.
214
     * Will return true if the token is found, and false if not or an error occurred.
215
     *
216
     * @param array   $tokens        The token array to search in
217
     * @param integer $searchedToken The token we search for, use PHP tokens here
218
     * @param integer $parsedEntity  The type of entity we search in front of, use PHP tokens here
219
     *
220
     * @return boolean
221
     */
222
    protected function hasSignatureToken(
223
        $tokens,
224
        $searchedToken,
225
        $parsedEntity
226
    ) {
227
        // We have to check what kind of structure we will check. Class and function are the only valid ones.
228
        if ($parsedEntity !== T_FUNCTION && $parsedEntity !== T_CLASS && $parsedEntity !== T_INTERFACE) {
229
            return false;
230
        }
231
232
        // Check the tokens
233
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
234
            // If we got the function name we have to check if we have the final keyword in front of it.
235
            // I would say should be within 6 tokens in front of the function keyword.
236
            if ($tokens[$i][0] === $parsedEntity) {
237
                // Check if our $i is lower than 6, if so we have to avoid getting into a negative range
238
                if ($i < 6) {
239
                    $i = 6;
240
                }
241
242
                for ($j = $i - 1; $j >= $i - 6; $j--) {
243
                    if ($tokens[$j][0] === $searchedToken) {
244
                        return true;
245
                    }
246
                }
247
248
                // We passed the 6 token loop but did not find something. So report it.
249
                return false;
250
            }
251
        }
252
253
        // We are still here? That should not be.
254
        return false;
255
    }
256
257
    /**
258
     * Will return the DocBlock of a certain construct based on the token identifying it.
259
     * Will return an empty string if none is found
260
     *
261
     * @param array   $tokens         The token array to search in
262
     * @param integer $structureToken The type of entity we search in front of, use PHP tokens here e.g. T_CLASS
263
     *
264
     * @return string
265
     */
266
    protected function getDocBlock($tokens, $structureToken)
267
    {
268
        // we need tokens which woudl break the construct and the DocBlock apart
269
        $blockBreakers = array_flip(array(T_CLASS, T_TRAIT, T_INTERFACE, T_FUNCTION));
270
271
        // the general assumption is:
272
        // We go to the first occurance of the structure token and traverse back until
273
        // we find a DocBlock without passing any breaks in the flow. This should be the correct block
274
        $tokenCount = count($tokens);
275
        for ($i = 0; $i < $tokenCount; $i++) {
276
            // if we passed the structure token
277
            if ($tokens[$i][0] === $structureToken) {
278
                // traverse back until we find the first DocBlock
279
                for ($j = ($i - 1); $j >= 0; $j--) {
280
                    if ($tokens[$j][0] === T_DOC_COMMENT) {
281
                        return $tokens[$j][1];
282
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
283
                    } elseif (isset($blockBreakers[$tokens[$j][0]])) {
284
                        // if we pass any other construct tokens or breaks we will fail
285
                        break;
286
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
287
                    } elseif ($tokens[$j][0] === T_WHITESPACE && substr_count($tokens[$j][1], "\n") > 1) {
288
                        // if there is a bigger linebreak in between construct and block we will fail too
289
                        break;
290
                    }
291
                }
292
293
                // still here? We did not find anything then
294
                break;
295
            }
296
        }
297
298
        // still here? That does not sound right
299
        return '';
300
    }
301
}
302