EnforcementFilter::generateCode()   B
last analyzed

Complexity

Conditions 8
Paths 16

Size

Total Lines 79

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 79
rs 7.2137
c 0
b 0
f 0
cc 8
nc 16
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * \AppserverIo\Doppelgaenger\StreamFilters\EnforcementFilter
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\StreamFilters;
22
23
use AppserverIo\Doppelgaenger\Config;
24
use AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition;
25
use AppserverIo\Doppelgaenger\Exceptions\ExceptionFactory;
26
use AppserverIo\Doppelgaenger\Exceptions\GeneratorException;
27
use AppserverIo\Doppelgaenger\Dictionaries\Placeholders;
28
use AppserverIo\Doppelgaenger\Dictionaries\ReservedKeywords;
29
use AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface;
30
use AppserverIo\Psr\MetaobjectProtocol\Dbc\Annotations\Processing;
31
32
/**
33
 * This filter will buffer the input stream and add the processing information into the prepared assertion checks
34
 * (see $dependencies)
35
 *
36
 * @author    Bernhard Wick <[email protected]>
37
 * @copyright 2015 TechDivision GmbH - <[email protected]>
38
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
39
 * @link      https://github.com/appserver-io/doppelgaenger
40
 * @link      http://www.appserver.io/
41
 */
42
class EnforcementFilter extends AbstractFilter
43
{
44
45
    /**
46
     * Order number if filters are used as a stack, higher means below others
47
     *
48
     * @const integer FILTER_ORDER
49
     */
50
    const FILTER_ORDER = 4;
51
52
    /**
53
     * Other filters on which we depend
54
     *
55
     * @var array $dependencies
56
     */
57
    protected $dependencies = array(array('PreconditionFilter', 'PostconditionFilter', 'InvariantFilter'));
58
59
    /**
60
     * The main filter method.
61
     * Implemented according to \php_user_filter class. Will loop over all stream buckets, buffer them and perform
62
     * the needed actions.
63
     *
64
     * @param resource $in       Incoming bucket brigade we need to filter
65
     * @param resource $out      Outgoing bucket brigade with already filtered content
66
     * @param integer  $consumed The count of altered characters as buckets pass the filter
67
     * @param boolean  $closing  Is the stream about to close?
68
     *
69
     * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException
70
     *
71
     * @return integer
72
     *
73
     * @link http://www.php.net/manual/en/php-user-filter.filter.php
74
     */
75
    public function filter($in, $out, &$consumed, $closing)
76
    {
77
        // Lets check if we got the config we wanted
78
        $config = $this->params['config'];
79
        $structureDefinition = $this->params['structureDefinition'];
80
81
        // check if we got what we need for proper processing
82
        if (!$config instanceof Config || !$structureDefinition instanceof StructureDefinitionInterface) {
83
            throw new GeneratorException('The enforcement filter needs the configuration as well as the definition of the currently filtered structure. At least one of these requirements is missing.');
84
        }
85
86
        // we need a valid configuration as well
87
        if (!$config->hasValue('enforcement/processing')) {
88
            throw new GeneratorException('Configuration does not contain the needed processing section.');
89
        }
90
91
        // get the default enforcement processing
92
        $localType = $this->filterLocalProcessing($structureDefinition->getDocBlock());
93
        $type = $localType ? $localType : $config->getValue('enforcement/processing');
94
95
        // Get the code for the processing
96
        $structureName = $structureDefinition->getQualifiedName();
97
        $structurePath = $structureDefinition->getPath();
98
        $preconditionCode = $this->generateCode($structureName, 'precondition', $type, $structurePath);
99
        $postconditionCode = $this->generateCode($structureName, 'postcondition', $type, $structurePath);
100
        $invariantCode = $this->generateCode($structureName, 'invariant', $type, $structurePath);
101
        $invalidCode = $this->generateCode($structureName, 'InvalidArgumentException', $type, $structurePath);
102
        $missingCode = $this->generateCode($structureName, 'MissingPropertyException', $type, $structurePath);
103
104
        // Get our buckets from the stream
105
        while ($bucket = stream_bucket_make_writeable($in)) {
106
            // Get the tokens
107
            $tokens = token_get_all($bucket->data);
108
109
            // Go through the tokens and check what we found
110
            $tokensCount = count($tokens);
111
            for ($i = 0; $i < $tokensCount; $i++) {
112
                // Did we find a function? If so check if we know that thing and insert the code of its preconditions.
113
                if (is_array($tokens[$i]) && $tokens[$i][0] === T_FUNCTION && is_array($tokens[$i + 2])) {
114
                    // Get the name of the function
115
                    $functionName = $tokens[$i + 2][1];
116
117
                    // Check if we got the function in our list, if not continue
118
                    $functionDefinition = $structureDefinition->getFunctionDefinitions()->get($functionName);
119
120
                    if (!$functionDefinition instanceof FunctionDefinition) {
121
                        continue;
122
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
123
                    } else {
124
                        // manage the injection of the enforcement code into the found function
125
                        $this->injectFunctionEnforcement(
126
                            $bucket->data,
127
                            $structureName,
128
                            $structurePath,
129
                            $preconditionCode,
130
                            $postconditionCode,
131
                            $functionDefinition
132
                        );
133
134
                        // "Destroy" code and function definition
135
                        $functionDefinition = null;
0 ignored issues
show
Unused Code introduced by
$functionDefinition is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
136
                    }
137
                }
138
            }
139
140
            // Insert the code for the static processing placeholders
141
            $bucket->data = str_replace(
142
                array(
143
                    Placeholders::ENFORCEMENT . 'invariant' . Placeholders::PLACEHOLDER_CLOSE,
144
                    Placeholders::ENFORCEMENT . 'InvalidArgumentException' . Placeholders::PLACEHOLDER_CLOSE,
145
                    Placeholders::ENFORCEMENT . 'MissingPropertyException' . Placeholders::PLACEHOLDER_CLOSE
146
                ),
147
                array($invariantCode, $invalidCode, $missingCode),
148
                $bucket->data
149
            );
150
151
            // Tell them how much we already processed, and stuff it back into the output
152
            $consumed += $bucket->datalen;
153
            stream_bucket_append($out, $bucket);
154
        }
155
156
        return PSFS_PASS_ON;
157
    }
158
159
    /**
160
     * Will try to filter custom local enforcement processing from a given docBloc.
161
     * Will return the found value, FALSE otherwise
162
     *
163
     * @param string $docBlock DocBloc to filter
164
     *
165
     * @return boolean|string
166
     */
167
    protected function filterLocalProcessing($docBlock)
168
    {
169
        // if the annotation cannot be found we have to do nothing here
170
        if (strpos($docBlock, '@' . Processing::ANNOTATION) === false) {
171
            return false;
172
        }
173
174
        // try to preg_match the right annotation
175
        $matches = array();
176
        preg_match_all('/@' . Processing::ANNOTATION . '\("(.+)"\)/', $docBlock, $matches);
177
        if (isset($matches[1])) {
178
            return array_pop($matches[1]);
179
        }
180
181
        // still here? Tell them we have failed then
182
        return false;
183
    }
184
185
    /**
186
     * /**
187
     * Will generate the code needed to enforce any broken assertion checks
188
     *
189
     * @param string $structureName The name of the structure for which we create the enforcement code
190
     * @param string $target        For which kind of assertion do wee need the processing
191
     * @param string $type          The enforcement processing type to generate code for
192
     * @param string $file          File for which the code gets generated
193
     *
194
     * @return string
195
     *
196
     * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException
197
     */
198
    protected function generateCode($structureName, $target, $type, $file = 'unknown')
199
    {
200
        $code = '';
201
202
        // Code defining the place the error happened
203
        $place = '__METHOD__';
204
205
        // If we are in an invariant we should tell them about the method we got called from
206
        $line = ReservedKeywords::START_LINE_VARIABLE;
207
        if ($target === 'invariant') {
208
            $place = ReservedKeywords::INVARIANT_CALLER_VARIABLE;
209
            $line = ReservedKeywords::ERROR_LINE_VARIABLE;
210
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
211
        } elseif ($target === 'postcondition') {
212
            $line = ReservedKeywords::END_LINE_VARIABLE;
213
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
214
        } elseif ($target === 'InvalidArgumentException' || $target === 'MissingPropertyException') {
215
            $line = 'isset($this->' . ReservedKeywords::ATTRIBUTE_STORAGE . '[$name])?$this->' . ReservedKeywords::ATTRIBUTE_STORAGE . '[$name][\'line\']:\'unknown\'';
216
        }
217
218
        // what we will always need is collection of all errors that occurred
219
        $errorCollectionCode = 'if (empty(' . ReservedKeywords::FAILURE_VARIABLE . ')) {
220
                        ' . ReservedKeywords::FAILURE_VARIABLE . ' = "";
221
                    } else {
222
                        ' . ReservedKeywords::FAILURE_VARIABLE . ' = \'Failed ' . $target . ' "\' . implode(\'" and "\', ' . ReservedKeywords::FAILURE_VARIABLE . ') . \'" in \' . ' . $place . ';
223
                    }
224
                    ' . ReservedKeywords::FAILURE_VARIABLE . ' .= implode(" and ", ' . ReservedKeywords::UNWRAPPED_FAILURE_VARIABLE . ');';
225
226
        // what kind of processing should we create?
227
        switch ($type) {
228
            case 'exception':
229
                $exceptionFactory = new ExceptionFactory();
230
                $exception = $exceptionFactory->getClassName($target);
231
232
                // Create the code
233
                $code .= '\AppserverIo\Doppelgaenger\ContractContext::close();
234
                    ' . $errorCollectionCode . '
235
                    $e = new \\' . $exception . '(' . ReservedKeywords::FAILURE_VARIABLE . ');
236
                    if ($e instanceof \AppserverIo\Doppelgaenger\Interfaces\ProxyExceptionInterface) {
237
                        $e->setLine(' . $line . ');
238
                        $e->setFile(\'' . $file . '\');
239
                    }
240
                    throw $e;';
241
242
                break;
243
244
            case 'logging':
245
                // Create the code
246
                $code .= $errorCollectionCode .
247
                    '$container = new \AppserverIo\Doppelgaenger\Utils\InstanceContainer();
248
                    $logger = @$container[\'' . ReservedKeywords::LOGGER_CONTAINER_ENTRY . '\'];
249
                    if (is_null($logger)) {
250
                        error_log(' . ReservedKeywords::FAILURE_VARIABLE . ');
251
                    } else {
252
                        $logger->error(' . ReservedKeywords::FAILURE_VARIABLE . ');
253
                    }';
254
255
                break;
256
257
            case 'none':
258
                // Create the code
259
                $code .= '\AppserverIo\Doppelgaenger\ContractContext::close();';
260
                break;
261
262
            default:
263
                // something went terribly wrong ...
264
                throw new GeneratorException(
265
                    sprintf(
266
                        'Unknown enforcement type "%s", please check configuration value "enforcement/processing" and %s annotations within %s',
267
                        $type,
268
                        Processing::ANNOTATION,
269
                        $structureName
270
                    )
271
                );
272
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
273
        }
274
275
        return $code;
276
    }
277
278
    /**
279
     * Will inject enforcement processing for a certain function.
280
     * Will take default processing code into account and check for custom processing configurations
281
     *
282
     * @param string             $bucketData         Payload of the currently filtered bucket
283
     * @param string             $structureName      The name of the structure for which we create the enforcement code
284
     * @param string             $structurePath      Path to the file containing the structure
285
     * @param string             $preconditionCode   Default precondition processing code
286
     * @param string             $postconditionCode  Default post-condition processing code
287
     * @param FunctionDefinition $functionDefinition Function definition to create the code for
288
     *
289
     * @return null
290
     */
291
    protected function injectFunctionEnforcement(
292
        & $bucketData,
293
        $structureName,
294
        $structurePath,
295
        $preconditionCode,
296
        $postconditionCode,
297
        FunctionDefinition $functionDefinition
298
    ) {
299
        $functionName = $functionDefinition->getName();
300
301
        // try to find a local enforcement processing configuration, if we find something we have to
302
        // create new enforcement code based on that information
303
        $localType = $this->filterLocalProcessing($functionDefinition->getDocBlock());
304
        if ($localType !== false) {
305
            // we found something, make a backup of default enforcement and generate the new code
306
            $preconditionCode = $this->generateCode($structureName, 'precondition', $localType, $structurePath);
307
            $postconditionCode = $this->generateCode($structureName, 'postcondition', $localType, $structurePath);
308
        }
309
310
        // Insert the code for the static processing placeholders
311
        $bucketData = str_replace(
312
            array(
313
                Placeholders::ENFORCEMENT . $functionName . 'precondition' . Placeholders::PLACEHOLDER_CLOSE,
314
                Placeholders::ENFORCEMENT . $functionName . 'postcondition' . Placeholders::PLACEHOLDER_CLOSE
315
            ),
316
            array($preconditionCode, $postconditionCode),
317
            $bucketData
318
        );
319
    }
320
}
321