1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* \AppserverIo\Doppelgaenger\StreamFilters\InvariantFilter |
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\Lists\AttributeDefinitionList; |
24
|
|
|
use AppserverIo\Doppelgaenger\Entities\Lists\TypedListList; |
25
|
|
|
use AppserverIo\Doppelgaenger\Exceptions\GeneratorException; |
26
|
|
|
use AppserverIo\Doppelgaenger\Dictionaries\Placeholders; |
27
|
|
|
use AppserverIo\Doppelgaenger\Dictionaries\ReservedKeywords; |
28
|
|
|
use AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* This filter will buffer the input stream and add all invariant 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 InvariantFilter extends AbstractFilter |
41
|
|
|
{ |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @const integer FILTER_ORDER Order number if filters are used as a stack, higher means below others |
45
|
|
|
*/ |
46
|
|
|
const FILTER_ORDER = 3; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var array $dependencies Other filters on which we depend |
50
|
|
|
*/ |
51
|
|
|
protected $dependencies = array('SkeletonFilter'); |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Filter a chunk of data by adding introductions to it |
55
|
|
|
* |
56
|
|
|
* @param string $chunk The data chunk to be filtered |
57
|
|
|
* @param StructureDefinitionInterface $structureDefinition Definition of the structure the chunk belongs to |
58
|
|
|
* |
59
|
|
|
* @return string |
60
|
|
|
*/ |
61
|
|
|
public function filterChunk($chunk, StructureDefinitionInterface $structureDefinition) |
62
|
|
|
{ |
63
|
|
|
// After iterate over the attributes and build up our array of attributes we have to include in our |
64
|
|
|
// checking mechanism. |
65
|
|
|
$obsoleteProperties = array(); |
66
|
|
|
$propertyReplacements = array(); |
67
|
|
|
$iterator = $structureDefinition->getAttributeDefinitions()->getIterator(); |
|
|
|
|
68
|
|
|
for ($i = 0; $i < $iterator->count(); $i++) { |
69
|
|
|
// Get the current attribute for more easy access |
70
|
|
|
$attribute = $iterator->current(); |
71
|
|
|
|
72
|
|
|
// Only enter the attribute if it is used in an invariant and it is not private |
73
|
|
|
if ($attribute->inInvariant() && $attribute->getVisibility() !== 'private') { |
74
|
|
|
// Build up our regex expression to filter them out |
75
|
|
|
$obsoleteProperties[] = '/' . $attribute->getVisibility() . '.*?\\' . $attribute->getName() . '/'; |
76
|
|
|
$propertyReplacements[] = 'private ' . $attribute->getName(); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
// Move the iterator |
80
|
|
|
$iterator->next(); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// Get our buckets from the stream |
84
|
|
|
$functionHook = ''; |
85
|
|
|
// We only have to do that once! |
86
|
|
|
if (empty($functionHook)) { |
87
|
|
|
$functionHook = Placeholders::STRUCTURE_END; |
88
|
|
|
|
89
|
|
|
// Get the code for our attribute storage |
90
|
|
|
$attributeCode = $this->generateAttributeCode($structureDefinition->getAttributeDefinitions()); |
|
|
|
|
91
|
|
|
|
92
|
|
|
// Get the code for the assertions |
93
|
|
|
$code = $this->generateFunctionCode($structureDefinition->getInvariants()); |
94
|
|
|
|
95
|
|
|
// Insert the code |
96
|
|
|
$chunk = str_replace( |
97
|
|
|
array( |
98
|
|
|
$functionHook, |
99
|
|
|
$functionHook |
100
|
|
|
), |
101
|
|
|
array( |
102
|
|
|
$functionHook . $attributeCode, |
103
|
|
|
$functionHook . $code |
104
|
|
|
), |
105
|
|
|
$chunk |
106
|
|
|
); |
107
|
|
|
|
108
|
|
|
// Determine if we need the __set method to be injected |
109
|
|
View Code Duplication |
if ($structureDefinition->getFunctionDefinitions()->entryExists('__set')) { |
|
|
|
|
110
|
|
|
// Get the code for our __set() method |
111
|
|
|
$setCode = $this->generateSetCode($structureDefinition->hasParents(), true); |
112
|
|
|
$chunk = str_replace( |
113
|
|
|
Placeholders::METHOD_INJECT . '__set' . Placeholders::PLACEHOLDER_CLOSE, |
114
|
|
|
$setCode, |
115
|
|
|
$chunk |
116
|
|
|
); |
117
|
|
|
|
|
|
|
|
118
|
|
|
} else { |
119
|
|
|
$setCode = $this->generateSetCode($structureDefinition->hasParents()); |
120
|
|
|
$chunk = str_replace( |
121
|
|
|
$functionHook, |
122
|
|
|
$functionHook . $setCode, |
123
|
|
|
$chunk |
124
|
|
|
); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
// Determine if we need the __get method to be injected |
128
|
|
View Code Duplication |
if ($structureDefinition->getFunctionDefinitions()->entryExists('__get')) { |
|
|
|
|
129
|
|
|
// Get the code for our __set() method |
130
|
|
|
$getCode = $this->generateGetCode($structureDefinition->hasParents(), true); |
131
|
|
|
$chunk = str_replace( |
132
|
|
|
Placeholders::METHOD_INJECT . '__get' . Placeholders::PLACEHOLDER_CLOSE, |
133
|
|
|
$getCode, |
134
|
|
|
$chunk |
135
|
|
|
); |
136
|
|
|
|
|
|
|
|
137
|
|
|
} else { |
138
|
|
|
$getCode = $this->generateGetCode($structureDefinition->hasParents()); |
139
|
|
|
$chunk = str_replace( |
140
|
|
|
$functionHook, |
141
|
|
|
$functionHook . $getCode, |
142
|
|
|
$chunk |
143
|
|
|
); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
// We need the code to call the invariant |
148
|
|
|
$this->injectInvariantCall($chunk); |
149
|
|
|
|
150
|
|
|
// Remove all the properties we will take care of with our magic setter and getter |
151
|
|
|
$chunk = preg_replace($obsoleteProperties, $propertyReplacements, $chunk, 1); |
152
|
|
|
|
153
|
|
|
|
154
|
|
|
return $chunk; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Will generate the code needed to for managing the attributes in regards to invariants related to them |
159
|
|
|
* |
160
|
|
|
* @param \AppserverIo\Doppelgaenger\Entities\Lists\AttributeDefinitionList $attributeDefinitions Defined attributes |
161
|
|
|
* |
162
|
|
|
* @return string |
163
|
|
|
*/ |
164
|
|
|
protected function generateAttributeCode(AttributeDefinitionList $attributeDefinitions) |
165
|
|
|
{ |
166
|
|
|
// We should create attributes to store our attribute types |
167
|
|
|
$code = ' |
168
|
|
|
/** |
169
|
|
|
* @var array |
170
|
|
|
*/ |
171
|
|
|
private $' . ReservedKeywords::ATTRIBUTE_STORAGE . ' = array( |
172
|
|
|
'; |
173
|
|
|
|
174
|
|
|
// After iterate over the attributes and build up our array |
175
|
|
|
$iterator = $attributeDefinitions->getIterator(); |
176
|
|
|
for ($i = 0; $i < $iterator->count(); $i++) { |
177
|
|
|
// Get the current attribute for more easy access |
178
|
|
|
$attribute = $iterator->current(); |
179
|
|
|
|
180
|
|
|
// Only enter the attribute if it is used in an invariant and it is not private |
181
|
|
|
if ($attribute->inInvariant() && $attribute->getVisibility() !== 'private') { |
182
|
|
|
$code .= '"' . substr($attribute->getName(), 1) . '"'; |
183
|
|
|
$code .= ' => array( |
184
|
|
|
"visibility" => "' . $attribute->getVisibility() . '", |
185
|
|
|
"line" => "' . $attribute->getLine() . '", |
186
|
|
|
'; |
187
|
|
|
// Now check if we need any keywords for the variable identity |
188
|
|
|
if ($attribute->isStatic()) { |
189
|
|
|
$code .= '"static" => true'; |
190
|
|
|
} else { |
191
|
|
|
$code .= '"static" => false'; |
192
|
|
|
} |
193
|
|
|
$code .= ' |
194
|
|
|
), |
195
|
|
|
'; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
// Move the iterator |
199
|
|
|
$iterator->next(); |
200
|
|
|
} |
201
|
|
|
$code .= '); |
202
|
|
|
'; |
203
|
|
|
|
204
|
|
|
return $code; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Will generate the code of the magic __set() method needed to check invariants related to member variables |
209
|
|
|
* |
210
|
|
|
* @param boolean $hasParents Does this structure have parents |
211
|
|
|
* @param boolean $injected Will the created method be injected or is it a stand alone method? |
212
|
|
|
* |
213
|
|
|
* @return string |
214
|
|
|
*/ |
215
|
|
|
protected function generateSetCode($hasParents, $injected = false) |
216
|
|
|
{ |
217
|
|
|
|
218
|
|
|
// We only need the method header if we don't inject |
219
|
|
|
if ($injected === false) { |
220
|
|
|
$code = '/** |
221
|
|
|
* Magic function to forward writing property access calls if within visibility boundaries. |
222
|
|
|
* |
223
|
|
|
* @throws \Exception |
224
|
|
|
*/ |
225
|
|
|
public function __set($name, $value) |
226
|
|
|
{'; |
227
|
|
|
} else { |
228
|
|
|
$code = ''; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$code .= ReservedKeywords::CONTRACT_CONTEXT . ' = \AppserverIo\Doppelgaenger\ContractContext::open(); |
232
|
|
|
' . ReservedKeywords::FAILURE_VARIABLE . ' = array(); |
233
|
|
|
' . ReservedKeywords::UNWRAPPED_FAILURE_VARIABLE . ' = array(); |
234
|
|
|
// Does this property even exist? If not, throw an exception |
235
|
|
|
if (!isset($this->' . ReservedKeywords::ATTRIBUTE_STORAGE . '[$name])) {'; |
236
|
|
|
|
237
|
|
View Code Duplication |
if ($hasParents) { |
|
|
|
|
238
|
|
|
$code .= 'return parent::__set($name, $value);'; |
239
|
|
|
} else { |
240
|
|
|
$code .= 'if (property_exists($this, $name)) {' . |
241
|
|
|
|
242
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name in an invalid way";' . |
243
|
|
|
Placeholders::ENFORCEMENT . 'InvalidArgumentException' . Placeholders::PLACEHOLDER_CLOSE . |
244
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
245
|
|
|
return false; |
246
|
|
|
} else {' . |
247
|
|
|
|
248
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name as it does not exist";' . |
249
|
|
|
Placeholders::ENFORCEMENT . 'MissingPropertyException' . Placeholders::PLACEHOLDER_CLOSE . |
250
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
251
|
|
|
return false; |
252
|
|
|
}'; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
$code .= '} |
256
|
|
|
// Check if the invariant holds |
257
|
|
|
' . Placeholders::INVARIANT_CALL . |
258
|
|
|
'// Now check what kind of visibility we would have |
259
|
|
|
$attribute = $this->' . ReservedKeywords::ATTRIBUTE_STORAGE . '[$name]; |
260
|
|
|
switch ($attribute["visibility"]) { |
261
|
|
|
|
262
|
|
|
case "protected" : |
263
|
|
|
|
264
|
|
|
if (is_subclass_of(get_called_class(), __CLASS__)) { |
265
|
|
|
|
266
|
|
|
$this->$name = $value; |
267
|
|
|
|
268
|
|
|
} else {' . |
269
|
|
|
|
270
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name in an invalid way";' . |
271
|
|
|
Placeholders::ENFORCEMENT . 'InvalidArgumentException' . Placeholders::PLACEHOLDER_CLOSE . |
272
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
273
|
|
|
return false; |
274
|
|
|
} |
275
|
|
|
break; |
276
|
|
|
|
277
|
|
|
case "public" : |
278
|
|
|
|
279
|
|
|
$this->$name = $value; |
280
|
|
|
break; |
281
|
|
|
|
282
|
|
|
default :' . |
283
|
|
|
|
284
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name in an invalid way";' . |
285
|
|
|
Placeholders::ENFORCEMENT . 'InvalidArgumentException' . Placeholders::PLACEHOLDER_CLOSE . |
286
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
287
|
|
|
return false; |
288
|
|
|
break; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
// Check if the invariant holds |
292
|
|
|
' . Placeholders::INVARIANT_CALL . |
293
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close();'; |
294
|
|
|
|
295
|
|
|
// We do not need the method encasing brackets if we inject |
296
|
|
|
if ($injected === false) { |
297
|
|
|
$code .= '}'; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
return $code; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Will generate the code of the magic __get() method needed to access member variables which are hidden |
305
|
|
|
* in order to force the usage of __set() |
306
|
|
|
* |
307
|
|
|
* @param boolean $hasParents Does this structure have parents |
308
|
|
|
* @param boolean $injected Will the created method be injected or is it a stand alone method? |
309
|
|
|
* |
310
|
|
|
* @return string |
311
|
|
|
*/ |
312
|
|
|
protected function generateGetCode($hasParents, $injected = false) |
313
|
|
|
{ |
314
|
|
|
|
315
|
|
|
// We only need the method header if we don't inject |
316
|
|
|
if ($injected === false) { |
317
|
|
|
$code = '/** |
318
|
|
|
* Magic function to forward reading property access calls if within visibility boundaries. |
319
|
|
|
* |
320
|
|
|
* @throws \Exception |
321
|
|
|
*/ |
322
|
|
|
public function __get($name) |
323
|
|
|
{'; |
324
|
|
|
} else { |
325
|
|
|
$code = ''; |
326
|
|
|
} |
327
|
|
|
$code .= ReservedKeywords::FAILURE_VARIABLE . ' = array(); |
328
|
|
|
' . ReservedKeywords::UNWRAPPED_FAILURE_VARIABLE . ' = array(); |
329
|
|
|
// Does this property even exist? If not, throw an exception |
330
|
|
|
if (!isset($this->' . ReservedKeywords::ATTRIBUTE_STORAGE . '[$name])) {'; |
331
|
|
|
|
332
|
|
View Code Duplication |
if ($hasParents) { |
|
|
|
|
333
|
|
|
$code .= 'return parent::__get($name);'; |
334
|
|
|
} else { |
335
|
|
|
$code .= 'if (property_exists($this, $name)) {' . |
336
|
|
|
|
337
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name in an invalid way";' . |
338
|
|
|
Placeholders::ENFORCEMENT . 'InvalidArgumentException' . Placeholders::PLACEHOLDER_CLOSE . |
339
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
340
|
|
|
return false; |
341
|
|
|
} else {' . |
342
|
|
|
|
343
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name as it does not exist";' . |
344
|
|
|
Placeholders::ENFORCEMENT . 'MissingPropertyException' . Placeholders::PLACEHOLDER_CLOSE . |
345
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
346
|
|
|
return false; |
347
|
|
|
}'; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
$code .= '} |
351
|
|
|
|
352
|
|
|
// Now check what kind of visibility we would have |
353
|
|
|
$attribute = $this->' . ReservedKeywords::ATTRIBUTE_STORAGE . '[$name]; |
354
|
|
|
switch ($attribute["visibility"]) { |
355
|
|
|
|
356
|
|
|
case "protected" : |
357
|
|
|
|
358
|
|
|
if (is_subclass_of(get_called_class(), __CLASS__)) { |
359
|
|
|
|
360
|
|
|
return $this->$name; |
361
|
|
|
|
362
|
|
|
} else {' . |
363
|
|
|
|
364
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name in an invalid way";' . |
365
|
|
|
Placeholders::ENFORCEMENT . 'InvalidArgumentException' . Placeholders::PLACEHOLDER_CLOSE . |
366
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
367
|
|
|
return false;} |
368
|
|
|
break; |
369
|
|
|
|
370
|
|
|
case "public" : |
371
|
|
|
|
372
|
|
|
return $this->$name; |
373
|
|
|
break; |
374
|
|
|
|
375
|
|
|
default :' . |
376
|
|
|
|
377
|
|
|
ReservedKeywords::FAILURE_VARIABLE . '[] = "accessing $name in an invalid way";' . |
378
|
|
|
Placeholders::ENFORCEMENT . 'InvalidArgumentException' . Placeholders::PLACEHOLDER_CLOSE . |
379
|
|
|
'\AppserverIo\Doppelgaenger\ContractContext::close(); |
380
|
|
|
return false; |
381
|
|
|
break; |
382
|
|
|
}'; |
383
|
|
|
|
384
|
|
|
// We do not need the method encasing brackets if we inject |
385
|
|
|
if ($injected === false) { |
386
|
|
|
$code .= '}'; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
return $code; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Will inject the call to the invariant checking method at encountered placeholder strings within the passed |
394
|
|
|
* bucket data |
395
|
|
|
* |
396
|
|
|
* @param string $bucketData Payload of the currently filtered bucket |
397
|
|
|
* |
398
|
|
|
* @return boolean |
399
|
|
|
*/ |
400
|
|
|
protected function injectInvariantCall(& $bucketData) |
401
|
|
|
{ |
402
|
|
|
$tmpMapping = array( |
403
|
|
|
Placeholders::INVARIANT_CALL => '\'unknown\'', |
404
|
|
|
Placeholders::INVARIANT_CALL_START => ReservedKeywords::START_LINE_VARIABLE, |
405
|
|
|
Placeholders::INVARIANT_CALL_END => ReservedKeywords::END_LINE_VARIABLE |
406
|
|
|
); |
407
|
|
|
|
408
|
|
|
foreach ($tmpMapping as $placeholder => $lineIndicator) { |
409
|
|
|
$code = 'if (' . ReservedKeywords::CONTRACT_CONTEXT . ' === true) { |
410
|
|
|
$this->' . ReservedKeywords::CLASS_INVARIANT . '(__METHOD__, ' . $lineIndicator . '); |
411
|
|
|
}'; |
412
|
|
|
|
413
|
|
|
// inject the clone statement to preserve an instance of the object prior to our call. |
414
|
|
|
$bucketData = str_replace( |
415
|
|
|
$placeholder, |
416
|
|
|
$code, |
417
|
|
|
$bucketData |
418
|
|
|
); |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
// Still here? We encountered no error then. |
422
|
|
|
return true; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Will generate the code needed to enforce made invariant assertions |
427
|
|
|
* |
428
|
|
|
* @param \AppserverIo\Doppelgaenger\Entities\Lists\TypedListList $assertionLists List of assertion lists |
429
|
|
|
* |
430
|
|
|
* @return string |
431
|
|
|
*/ |
432
|
|
|
protected function generateFunctionCode(TypedListList $assertionLists) |
433
|
|
|
{ |
434
|
|
|
$code = 'protected function ' . ReservedKeywords::CLASS_INVARIANT . '(' . ReservedKeywords::INVARIANT_CALLER_VARIABLE . ', ' . ReservedKeywords::ERROR_LINE_VARIABLE . ') { |
435
|
|
|
' . ReservedKeywords::CONTRACT_CONTEXT . ' = \AppserverIo\Doppelgaenger\ContractContext::open(); |
436
|
|
|
if (' . ReservedKeywords::CONTRACT_CONTEXT . ') { |
437
|
|
|
' . ReservedKeywords::FAILURE_VARIABLE . ' = array(); |
438
|
|
|
' . ReservedKeywords::UNWRAPPED_FAILURE_VARIABLE . ' = array();'; |
439
|
|
|
|
440
|
|
|
$conditionCounter = 0; |
441
|
|
|
$invariantIterator = $assertionLists->getIterator(); |
442
|
|
View Code Duplication |
for ($i = 0; $i < $invariantIterator->count(); $i++) { |
|
|
|
|
443
|
|
|
// Create the inner loop for the different assertions |
444
|
|
|
if ($invariantIterator->current()->count() !== 0) { |
445
|
|
|
$assertionIterator = $invariantIterator->current()->getIterator(); |
446
|
|
|
|
447
|
|
|
// collect all assertion code for assertions of this instance |
448
|
|
|
for ($j = 0; $j < $assertionIterator->count(); $j++) { |
449
|
|
|
// Code to catch failed assertions |
450
|
|
|
$code .= $assertionIterator->current()->toCode(); |
451
|
|
|
$assertionIterator->next(); |
452
|
|
|
$conditionCounter++; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
// generate the check for assertions results |
456
|
|
|
if ($conditionCounter > 0) { |
457
|
|
|
$code .= 'if (!empty(' . ReservedKeywords::FAILURE_VARIABLE . ') || !empty(' . ReservedKeywords::UNWRAPPED_FAILURE_VARIABLE . ')) { |
458
|
|
|
' . Placeholders::ENFORCEMENT . 'invariant' . Placeholders::PLACEHOLDER_CLOSE . ' |
459
|
|
|
}'; |
460
|
|
|
} |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
// increment the outer loop |
464
|
|
|
$invariantIterator->next(); |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
$code .= '} |
468
|
|
|
\AppserverIo\Doppelgaenger\ContractContext::close(); |
469
|
|
|
}'; |
470
|
|
|
|
471
|
|
|
return $code; |
472
|
|
|
} |
473
|
|
|
} |
474
|
|
|
|
Let’s take a look at an example:
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 implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: