Completed
Pull Request — master (#40)
by Bernhard
04:14
created

PostconditionFilter::filterChunk()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 43
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 43
rs 8.439
cc 6
eloc 20
nc 4
nop 2
1
<?php
2
3
/**
4
 * \AppserverIo\Doppelgaenger\StreamFilters\PostconditionFilter
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\Entities\Definitions\FunctionDefinition;
24
use AppserverIo\Doppelgaenger\Entities\Lists\FunctionDefinitionList;
25
use AppserverIo\Doppelgaenger\Entities\Lists\TypedListList;
26
use AppserverIo\Doppelgaenger\Exceptions\GeneratorException;
27
use AppserverIo\Doppelgaenger\Dictionaries\Placeholders;
28
use AppserverIo\Doppelgaenger\Dictionaries\ReservedKeywords;
29
30
/**
31
 * This filter will buffer the input stream and add all postcondition related information at prepared locations
32
 * (see $dependencies)
33
 *
34
 * @author    Bernhard Wick <[email protected]>
35
 * @copyright 2015 TechDivision GmbH - <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/appserver-io/doppelgaenger
38
 * @link      http://www.appserver.io/
39
 */
40
class PostconditionFilter extends AbstractFilter
41
{
42
    /**
43
     * @const integer FILTER_ORDER Order number if filters are used as a stack, higher means below others
44
     */
45
    const FILTER_ORDER = 2;
46
47
    /**
48
     * @var array $dependencies Other filters on which we depend
49
     */
50
    protected $dependencies = array('SkeletonFilter');
51
52
    /**
53
     * Filter a chunk of data by adding postcondition checks
54
     *
55
     * @param string                 $chunk               The data chunk to be filtered
56
     * @param FunctionDefinitionList $functionDefinitions Definition of the structure the chunk belongs to
57
     *
58
     * @return string
59
     */
60
    public function filterChunk($chunk, FunctionDefinitionList $functionDefinitions)
61
    {
62
        // Get the tokens
63
        $tokens = token_get_all($chunk);
64
65
        // Go through the tokens and check what we found
66
        $tokensCount = count($tokens);
67
        for ($i = 0; $i < $tokensCount; $i++) {
68
            // Did we find a function? If so check if we know that thing and insert the code of its preconditions.
69
            if (is_array($tokens[$i]) && $tokens[$i][0] === T_FUNCTION && is_array($tokens[$i + 2])) {
70
                // Get the name of the function
71
                $functionName = $tokens[$i + 2][1];
72
73
                // Check if we got the function in our list, if not continue
74
                $functionDefinition = $functionDefinitions->get($functionName);
75
76
                if (!$functionDefinition instanceof FunctionDefinition) {
77
                    continue;
78
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
79
                } else {
80
                    // If we use the old notation we have to insert the statement to make a copy
81
                    $this->injectOldCode($chunk, $functionDefinition);
82
83
                    // Get the code for the assertions
84
                    $code = $this->generateCode($functionDefinition->getAllPostconditions(), $functionName);
85
86
                    // Insert the code
87
                    $chunk = str_replace(
88
                        Placeholders::POSTCONDITION . $functionDefinition->getName() .
89
                        Placeholders::PLACEHOLDER_CLOSE,
90
                        $code,
91
                        $chunk
92
                    );
93
94
                    // "Destroy" code and function definition
95
                    $code = null;
0 ignored issues
show
Unused Code introduced by
$code 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...
96
                    $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...
97
                }
98
            }
99
        }
100
101
        return $chunk;
102
    }
103
104
    /**
105
     * Will change code to create an entry for the old object state.
106
     *
107
     * @param string                                                             $bucketData         Payload of the currently
108
     *                                                                                       filtered bucket
109
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition $functionDefinition Currently handled function
110
     *
111
     * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException
112
     *
113
     * @return boolean
114
     */
115
    protected function injectOldCode(& $bucketData, FunctionDefinition & $functionDefinition)
116
    {
117
        // Do we even need to do anything?
118
        if ($functionDefinition->usesOld() !== true) {
119
            return false;
120
        }
121
        // If the function is static it should not use the dgOld keyword as there is no state to the class!
122
        if ($functionDefinition->isStatic() === true) {
123
            throw new GeneratorException(sprintf('Cannot clone class state in static method %s.', $functionDefinition->getName()));
124
        }
125
126
        // Still here? Then inject the clone statement to preserve an instance of the object prior to our call.
127
        $bucketData = str_replace(
128
            Placeholders::OLD_SETUP . $functionDefinition->getName() .
129
            Placeholders::PLACEHOLDER_CLOSE,
130
            ReservedKeywords::OLD . ' = clone $this;',
131
            $bucketData
132
        );
133
134
        // Still here? We encountered no error then.
135
        return true;
136
    }
137
138
    /**
139
     * Will generate the code needed to enforce made postcondition assertions
140
     *
141
     * @param \AppserverIo\Doppelgaenger\Entities\Lists\TypedListList $assertionLists List of assertion lists
142
     * @param string                                                  $functionName   The name of the function for which we create the enforcement code
143
     *
144
     * @return string
145
     */
146
    protected function generateCode(TypedListList $assertionLists, $functionName)
147
    {
148
        // We only use contracting if we're not inside another contract already
149
        $code = '/* BEGIN OF POSTCONDITION ENFORCEMENT */
150
            if (' . ReservedKeywords::CONTRACT_CONTEXT . ') {
151
                ' . ReservedKeywords::FAILURE_VARIABLE . ' = array();
152
                ' . ReservedKeywords::UNWRAPPED_FAILURE_VARIABLE . ' = array();
153
154
                ';
155
156
        // We need a counter to check how much conditions we got
157
        $conditionCounter = 0;
158
        $listIterator = $assertionLists->getIterator();
159 View Code Duplication
        for ($i = 0; $i < $listIterator->count(); $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
160
            // Create the inner loop for the different assertions
161
            $assertionIterator = $listIterator->current()->getIterator();
162
163
            // Only act if we got actual entries
164
            if ($assertionIterator->count() === 0) {
165
                // increment the outer loop
166
                $listIterator->next();
167
                continue;
168
            }
169
170
            // collect all assertion code for assertions of this instance
171
            for ($j = 0; $j < $assertionIterator->count(); $j++) {
172
                // Code to catch failed assertions
173
                $code .=  $assertionIterator->current()->toCode();
174
                $assertionIterator->next();
175
                $conditionCounter++;
176
            }
177
178
            // generate the check for assertions results
179
            if ($conditionCounter > 0) {
180
                $code .= '    if (!empty(' . ReservedKeywords::FAILURE_VARIABLE . ') || !empty(' . ReservedKeywords::UNWRAPPED_FAILURE_VARIABLE . ')) {
181
                    ' . Placeholders::ENFORCEMENT . $functionName . 'postcondition' . Placeholders::PLACEHOLDER_CLOSE . '
182
                }
183
            ';
184
            }
185
186
            // increment the outer loop
187
            $listIterator->next();
188
        }
189
190
        // Closing bracket for contract depth check
191
        $code .= '}
192
            ' . '/* END OF POSTCONDITION ENFORCEMENT */';
193
194
        // Did we get anything at all? If not only give back a comment.
195
        if ($conditionCounter === 0) {
196
            $code = '/* No postconditions for this function/method */';
197
        }
198
199
        return $code;
200
    }
201
}
202