Generator   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 440
Duplicated Lines 8.64 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 12
dl 38
loc 440
rs 9.84
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A create() 0 45 3
A createFilePath() 0 7 1
A createFileFromDefinition() 0 19 2
A createFileFromAspectDefinition() 0 9 1
A createFileFromInterfaceDefinition() 0 19 1
A createFileFromArbitraryDefinition() 0 38 2
F appendDefaultFilters() 38 111 16
A appendFilter() 0 17 2
A getFileName() 0 11 2
A update() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/**
4
 * \AppserverIo\Doppelgaenger\Generator
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;
22
23
use AppserverIo\Doppelgaenger\Entities\Definitions\AbstractStructureDefinition;
24
use AppserverIo\Doppelgaenger\Entities\Definitions\AspectDefinition;
25
use AppserverIo\Doppelgaenger\Exceptions\GeneratorException;
26
use AppserverIo\Doppelgaenger\Entities\Definitions\ClassDefinition;
27
use AppserverIo\Doppelgaenger\Entities\Definitions\InterfaceDefinition;
28
use AppserverIo\Doppelgaenger\Entities\Definitions\StructureDefinitionHierarchy;
29
use AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface;
30
use AppserverIo\Doppelgaenger\Entities\Definitions\Structure;
31
use AppserverIo\Doppelgaenger\Parser\StructureParserFactory;
32
use AppserverIo\Doppelgaenger\Dictionaries\Placeholders;
33
34
/**
35
 * This class initiates the creation of enforced structure definitions.
36
 *
37
 * @author    Bernhard Wick <[email protected]>
38
 * @copyright 2015 TechDivision GmbH - <[email protected]>
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/appserver-io/doppelgaenger
41
 * @link      http://www.appserver.io/
42
 */
43
class Generator
44
{
45
46
    /**
47
     * The register for any known aspects
48
     *
49
     * @var \AppserverIo\Doppelgaenger\AspectRegister $aspectRegister
50
     */
51
    protected $aspectRegister;
52
53
    /**
54
     * A cacheMap instance to organize our cache
55
     *
56
     * @var \AppserverIo\Doppelgaenger\CacheMap $cacheMap
57
     */
58
    protected $cacheMap;
59
60
    /**
61
     * A structureMap instance to organize the known structures
62
     *
63
     * @var \AppserverIo\Doppelgaenger\StructureMap $structureMap
64
     */
65
    protected $structureMap;
66
67
    /**
68
     * The aspect of the configuration we need
69
     *
70
     * @var \AppserverIo\Doppelgaenger\Config $config
71
     */
72
    protected $config;
73
74
    /**
75
     * Collection of definitions and their inheritance relation to each other
76
     *
77
     * @var \AppserverIo\Doppelgaenger\Entities\Definitions\StructureDefinitionHierarchy $structureDefinitionHierarchy
78
     */
79
    protected $structureDefinitionHierarchy;
80
81
    /**
82
     * Default constructor
83
     *
84
     * @param \AppserverIo\Doppelgaenger\StructureMap   $structureMap   A structureMap instance to organize the known structures
85
     * @param \AppserverIo\Doppelgaenger\CacheMap       $cacheMap       A cacheMap instance to organize our cache
86
     * @param \AppserverIo\Doppelgaenger\Config         $config         Configuration
87
     * @param \AppserverIo\Doppelgaenger\AspectRegister $aspectRegister The register for known aspects
88
     */
89
    public function __construct(StructureMap $structureMap, CacheMap $cacheMap, Config $config, AspectRegister $aspectRegister)
90
    {
91
        $this->cacheMap = $cacheMap;
92
        $this->structureMap = $structureMap;
93
        $this->config = $config;
94
        $this->aspectRegister = $aspectRegister;
95
        $this->structureDefinitionHierarchy = new StructureDefinitionHierarchy();
96
    }
97
98
    /**
99
     * Will create an altered definition of the structure defined in the $mapEntry variable.
100
     * Will also add it to the cache map
101
     *
102
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\Structure $mapEntry        Entry of a StructureMap we want created
103
     * @param boolean                                                   $createRecursive If contract inheritance is enabled
104
     *
105
     * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException
106
     *
107
     * @return boolean
108
     */
109
    public function create(Structure $mapEntry, $createRecursive = false)
110
    {
111
        // We know what we are searching for and we got a fine factory so lets get us a parser
112
        $structureParserFactory = new StructureParserFactory();
113
        $parser = $structureParserFactory->getInstance(
114
            $mapEntry->getType(),
115
            $mapEntry->getPath(),
116
            $this->config,
117
            $this->structureMap,
118
            $this->structureDefinitionHierarchy
119
        );
120
121
        // Lets get the definition we are looking for
122
        $structureDefinition = $parser->getDefinition($mapEntry->getIdentifier(), $createRecursive);
123
124
        if (!$structureDefinition instanceof StructureDefinitionInterface) {
125
            // we did not get what we need, so fail
126
            return false;
127
        }
128
129
        $qualifiedName = $structureDefinition->getQualifiedName();
130
        $filePath = $this->createFilePath(
131
            $qualifiedName,
132
            $mapEntry->getPath()
0 ignored issues
show
Unused Code introduced by
The call to Generator::createFilePath() has too many arguments starting with $mapEntry->getPath().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
133
        );
134
135
        $tmp = $this->createFileFromDefinition($filePath, $structureDefinition);
136
137
        if ($tmp === false) {
138
            // we were not able to create a new definition file, fail
139
            throw new GeneratorException(sprintf('Could not create altered definition for %s', $qualifiedName));
140
        }
141
        // Now get our new file into the cacheMap
142
        $this->cacheMap->add(
143
            new Structure(
144
                filectime($mapEntry->getPath()),
145
                $qualifiedName,
146
                $filePath,
147
                $structureDefinition->getType()
148
            )
149
        );
150
151
        // Still here? Than everything worked out great.
152
        return true;
153
    }
154
155
    /**
156
     * Will return the path the cached and altered definition will have
157
     *
158
     * @param string $structureName Name of the structure we want to update
159
     *
160
     * @return string
161
     *
162
     * TODO implement this somewhere more accessible, others might need it too (e.g. autoloader)
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
163
     */
164
    protected function createFilePath($structureName)
165
    {
166
        // s a file can contain multiple structures we will substitute the filename with the structure name
167
        $tmpFileName = ltrim(str_replace('\\', '_', $structureName), '_');
168
169
        return $this->config->getValue('cache/dir') . DIRECTORY_SEPARATOR . $tmpFileName . '.php';
170
    }
171
172
    /**
173
     * Will create a file containing the altered definition
174
     *
175
     * @param string                                                             $targetFileName      The intended name of the
176
     *                                                                                       new file
177
     * @param \AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface $structureDefinition The definition of the
178
     *                                                                                       structure we will alter
179
     *
180
     * @throws \InvalidArgumentException
181
     *
182
     * @return boolean
183
     */
184
    protected function createFileFromDefinition(
185
        $targetFileName,
186
        StructureDefinitionInterface $structureDefinition
187
    ) {
188
        // We have to check which structure type we got
189
        $definitionType = get_class($structureDefinition);
190
191
        // Call the method accordingly
192
        $tmp = explode('\\', $definitionType);
193
        $creationMethod = 'createFileFrom' . array_pop($tmp);
194
195
        // Check if we got something, if not we will default to class
196
        if (!method_exists($this, $creationMethod)) {
197
            // per default we will try to create a class definition
198
            $creationMethod = 'createFileFromArbitraryDefinition';
199
        }
200
201
        return $this->$creationMethod($targetFileName, $structureDefinition);
202
    }
203
204
    /**
205
     * Will create a file with the altered class definition as its content.
206
     * We will register the aspect first
207
     *
208
     * @param string                                                           $targetFileName   The intended name of the new file
209
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\AspectDefinition $aspectDefinition The definition of the structure we will alter
210
     *
211
     * @return boolean
212
     */
213
    protected function createFileFromAspectDefinition($targetFileName, AspectDefinition $aspectDefinition)
214
    {
215
216
        // register the aspect in our central aspect register
217
        $this->aspectRegister->register($aspectDefinition);
218
219
        // create the new definition
220
        return $this->createFileFromArbitraryDefinition($targetFileName, $aspectDefinition);
221
    }
222
223
    /**
224
     * Will create a file for a given interface definition.
225
     * We will just copy the file here until the autoloader got refactored.
226
     *
227
     * @param string                                                              $targetFileName      The intended name of the new file
228
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\InterfaceDefinition $structureDefinition The definition of the structure we will alter
229
     *
230
     * @return boolean
231
     *
232
     * TODO remove when autoloader is able to recognize and skip interfaces
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
233
     */
234
    protected function createFileFromInterfaceDefinition(
235
        $targetFileName,
236
        InterfaceDefinition $structureDefinition
237
    ) {
238
        // Get the content of the file
239
        $content = file_get_contents($structureDefinition->getPath());
240
241
        // Make the one change we need, the original file path and modification timestamp
242
        $content = str_replace(
243
            '<?php',
244
            '<?php /* ' . Placeholders::ORIGINAL_PATH_HINT . $structureDefinition->getPath() . '#' .
245
            filemtime(
246
                $structureDefinition->getPath()
247
            ) . Placeholders::ORIGINAL_PATH_HINT . ' */',
248
            $content
249
        );
250
251
        return (boolean)file_put_contents($targetFileName, $content);
252
    }
253
254
    /**
255
     * Will create a file with the altered class definition as its content
256
     *
257
     * @param string                                                                      $targetFileName      The intended name of the new file
258
     * @param \AppserverIo\Doppelgaenger\Entities\Definitions\AbstractStructureDefinition $structureDefinition The definition of the structure we will alter
259
     *
260
     * @return boolean
261
     */
262
    protected function createFileFromArbitraryDefinition(
263
        $targetFileName,
0 ignored issues
show
Unused Code introduced by
The parameter $targetFileName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
264
        AbstractStructureDefinition $structureDefinition
265
    ) {
266
267
        $res = fopen(
268
            $this->createFilePath($structureDefinition->getQualifiedName()),
269
            'w+'
270
        );
271
272
        // Append all configured filters
273
        $this->appendDefaultFilters($res, $structureDefinition);
274
275
        $tmp = fwrite(
276
            $res,
277
            file_get_contents($structureDefinition->getPath(), time())
278
        );
279
280
        // Did we write something?
281
        if ($tmp > 0) {
282
            fclose($res);
283
284
            return true;
285
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
286
        } else {
287
            // Delete the empty file stub we made
288
            unlink(
289
                $this->createFilePath(
290
                    $structureDefinition->getQualifiedName()
291
                ),
292
                $res
293
            );
294
295
            fclose($res);
296
297
            return false;
298
        }
299
    }
300
301
    /**
302
     * Will append all needed filters based on the enforcement level stated in the configuration file.
303
     *
304
     * @param resource                                                           $res                 The resource we will append the filters to
305
     * @param \AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface $structureDefinition Structure definition providing needed information
306
     *
307
     * @return array
308
     */
309
    protected function appendDefaultFilters(
310
        & $res,
311
        StructureDefinitionInterface $structureDefinition
312
    ) {
313
        // resulting array with resources of appended filters
314
        $filters = array();
315
316
        // Lets get the enforcement level
317
        $levelArray = array();
318
        if ($this->config->hasValue('enforcement/level')) {
319
            $levelArray = array_reverse(str_split(decbin($this->config->getValue('enforcement/level'))));
320
        }
321
322
        // Whatever the enforcement level is, we will always need the skeleton filter.
323
        $filters['SkeletonFilter'] = $this->appendFilter(
324
            $res,
325
            'AppserverIo\Doppelgaenger\StreamFilters\SkeletonFilter',
326
            $structureDefinition
327
        );
328
329
        // Now lets register and append the filters if they are mapped to a 1
330
        // Lets have a look at the precondition filter first
331 View Code Duplication
        if (isset($levelArray[0]) && $levelArray[0] == 1) {
332
            // Do we even got any preconditions?
333
            $filterNeeded = false;
334
            $iterator = $structureDefinition->getFunctionDefinitions()->getIterator();
335
            foreach ($iterator as $functionDefinition) {
336
                if ($functionDefinition->getAllPreconditions()->count() !== 0) {
337
                    $filterNeeded = true;
338
                    break;
339
                }
340
            }
341
342
            if ($filterNeeded) {
343
                $filters['PreconditionFilter'] = $this->appendFilter(
344
                    $res,
345
                    'AppserverIo\Doppelgaenger\StreamFilters\PreconditionFilter',
346
                    $structureDefinition->getFunctionDefinitions()
347
                );
348
            }
349
        }
350
351
        // What about the post-condition filter?
352 View Code Duplication
        if (isset($levelArray[1]) && $levelArray[1] == 1) {
353
            // Do we even got any post-conditions?
354
            $filterNeeded = false;
355
            $iterator = $structureDefinition->getFunctionDefinitions()->getIterator();
356
            foreach ($iterator as $functionDefinition) {
357
                if ($functionDefinition->getAllPostconditions()->count() !== 0) {
358
                    $filterNeeded = true;
359
                    break;
360
                }
361
            }
362
363
            if ($filterNeeded) {
364
                $filters['PostconditionFilter'] = $this->appendFilter(
365
                    $res,
366
                    'AppserverIo\Doppelgaenger\StreamFilters\PostconditionFilter',
367
                    $structureDefinition->getFunctionDefinitions()
368
                );
369
            }
370
        }
371
372
        // What about the invariant filter?
373
        if (isset($levelArray[2]) && $levelArray[2] == 1) {
374
            // Do we even got any invariants?
375
            if ($structureDefinition->getInvariants()->count(true) !== 0) {
376
                $filters['InvariantFilter'] = $this->appendFilter(
377
                    $res,
378
                    'AppserverIo\Doppelgaenger\StreamFilters\InvariantFilter',
379
                    $structureDefinition
380
                );
381
            }
382
        }
383
384
        // introductions make only sense for classes
385
        if ($structureDefinition instanceof ClassDefinition) {
386
            // add the filter used for introductions
387
            $filters['IntroductionFilter'] = $this->appendFilter(
388
                $res,
389
                'AppserverIo\Doppelgaenger\StreamFilters\IntroductionFilter',
390
                $structureDefinition->getIntroductions()
391
            );
392
        }
393
394
        // add the filter we need for our AOP advices
395
        $filters['AdviceFilter'] = $this->appendFilter(
396
            $res,
397
            'AppserverIo\Doppelgaenger\StreamFilters\AdviceFilter',
398
            array('functionDefinitions' => $structureDefinition->getFunctionDefinitions(), 'aspectRegister' => $this->aspectRegister)
399
        );
400
401
        // add the filter used to proxy to the actual implementation
402
        $filters['ProcessingFilter'] = $this->appendFilter(
403
            $res,
404
            'AppserverIo\Doppelgaenger\StreamFilters\ProcessingFilter',
405
            $structureDefinition->getFunctionDefinitions()
406
        );
407
408
        // We ALWAYS need the enforcement filter. Everything else would not make any sense
409
        $filters['EnforcementFilter'] = $this->appendFilter(
410
            $res,
411
            'AppserverIo\Doppelgaenger\StreamFilters\EnforcementFilter',
412
            array('structureDefinition' => $structureDefinition, 'config' => $this->config)
413
        );
414
415
        // at last we want to make the output beatiful and detect sysntax errors
416
        // $filters['BeautifyFilter'] = $this->appendFilter($res, 'AppserverIo\Doppelgaenger\StreamFilters\BeautifyFilter', array());
417
418
        return $filters;
419
    }
420
421
    /**
422
     * Will append a given filter to a resource.
423
     * Might fail if the filter cannot be found.
424
     * Will return true if filter got appended successfully
425
     *
426
     * @param resource $res         The resource we will append the filters to
427
     * @param string   $filterClass The fully qualified name of the filter class
428
     * @param mixed    $params      Whatever params the filter might need
429
     *
430
     * @return resource
431
     *
432
     * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException
433
     */
434
    public function appendFilter(& $res, $filterClass, $params)
435
    {
436
        // check if the given filter exists and throw an exception if not
437
        if (!class_exists($filterClass)) {
438
            throw new GeneratorException(sprintf('Could not find filter class %s', $filterClass));
439
        }
440
441
        // append the filter to the given resource
442
        $filterName = substr(strrchr($filterClass, '\\'), 1);
443
        stream_filter_register($filterName, $filterClass);
444
        return stream_filter_append(
445
            $res,
446
            $filterName,
447
            STREAM_FILTER_WRITE,
448
            $params
449
        );
450
    }
451
452
    /**
453
     * Return the cache path (as organized by our cache map) for a given structure name
454
     *
455
     * @param string $structureName The structure we want the cache path for
456
     *
457
     * @return boolean|string
458
     */
459
    public function getFileName($structureName)
460
    {
461
        $mapEntry = $this->cacheMap->getEntry($structureName);
462
463
        if (!$mapEntry instanceof Structure) {
464
            // we should fail if we do not get a structure
465
            return false;
466
        }
467
468
        return $mapEntry->getPath();
469
    }
470
471
    /**
472
     * Method used to update certain structures
473
     *
474
     * @param string $structureName Name of the structure we want to update
475
     *
476
     * @return boolean
477
     */
478
    public function update($structureName)
479
    {
480
        return $this->create($structureName, true);
0 ignored issues
show
Documentation introduced by
$structureName is of type string, but the function expects a object<AppserverIo\Doppe...\Definitions\Structure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
481
    }
482
}
483