Completed
Push — 1.0.x ( 17be5e...3a014b )
by Maciej
08:48
created

generateAssociationMappingPropertyDocBlock()   F

Complexity

Conditions 15
Paths 662

Size

Total Lines 49
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 22.0863
Metric Value
dl 0
loc 49
ccs 26
cts 38
cp 0.6842
rs 2.8539
cc 15
eloc 34
nc 662
nop 2
crap 22.0863

How to fix   Complexity   

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\Tools;
21
22
use Doctrine\Common\Inflector\Inflector;
23
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
24
use Doctrine\ODM\MongoDB\Types\Type;
25
26
/**
27
 * Generic class used to generate PHP5 document classes from ClassMetadataInfo instances
28
 *
29
 *     [php]
30
 *     $classes = $dm->getClassMetadataInfoFactory()->getAllMetadata();
31
 *
32
 *     $generator = new \Doctrine\ODM\MongoDB\Tools\DocumentGenerator();
33
 *     $generator->setGenerateAnnotations(true);
34
 *     $generator->setGenerateStubMethods(true);
35
 *     $generator->setRegenerateDocumentIfExists(false);
36
 *     $generator->setUpdateDocumentIfExists(true);
37
 *     $generator->generate($classes, '/path/to/generate/documents');
38
 *
39
 * @since   1.0
40
 * @author  Benjamin Eberlei <[email protected]>
41
 * @author  Guilherme Blanco <[email protected]>
42
 * @author  Jonathan Wage <[email protected]>
43
 * @author  Roman Borschel <[email protected]>
44
 */
45
class DocumentGenerator
46
{
47
    /**
48
     * @var bool
49
     */
50
    private $backupExisting = true;
51
52
    /** The extension to use for written php files */
53
    private $extension = '.php';
54
55
    /** Whether or not the current ClassMetadataInfo instance is new or old */
56
    private $isNew = true;
57
58
    private $staticReflection = array();
59
60
    /** Number of spaces to use for indention in generated code */
61
    private $numSpaces = 4;
62
63
    /** The actual spaces to use for indention */
64
    private $spaces = '    ';
65
66
    /** The class all generated documents should extend */
67
    private $classToExtend;
68
69
    /** Whether or not to generate annotations */
70
    private $generateAnnotations = false;
71
72
    /** Whether or not to generate stub methods */
73
    private $generateDocumentStubMethods = false;
74
75
    /** Whether or not to update the document class if it exists already */
76
    private $updateDocumentIfExists = false;
77
78
    /** Whether or not to re-generate document class if it exists already */
79
    private $regenerateDocumentIfExists = false;
80
81
    private static $classTemplate =
82
'<?php
83
84
<namespace>
85
86
<imports>
87
88
<documentAnnotation>
89
<documentClassName>
90
{
91
<documentBody>
92
}';
93
94
    private static $getMethodTemplate =
95
'/**
96
 * <description>
97
 *
98
 * @return <variableType>$<variableName>
99
 */
100
public function <methodName>()
101
{
102
<spaces>return $this-><fieldName>;
103
}';
104
105
    private static $setMethodTemplate =
106
'/**
107
 * <description>
108
 *
109
 * @param <variableType>$<variableName>
110
 * @return self
111
 */
112
public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
113
{
114
<spaces>$this-><fieldName> = $<variableName>;
115
<spaces>return $this;
116
}';
117
118
    private static $addMethodTemplate =
119
'/**
120
 * <description>
121
 *
122
 * @param <variableType>$<variableName>
123
 */
124
public function <methodName>(<methodTypeHint>$<variableName>)
125
{
126
<spaces>$this-><fieldName>[] = $<variableName>;
127
}';
128
129
    private static $removeMethodTemplate =
130
'/**
131
 * <description>
132
 *
133
 * @param <variableType>$<variableName>
134
 */
135
public function <methodName>(<methodTypeHint>$<variableName>)
136
{
137
<spaces>$this-><fieldName>->removeElement($<variableName>);
138
}';
139
140
    private static $lifecycleCallbackMethodTemplate =
141
'<comment>
142
public function <methodName>()
143
{
144
<spaces>// Add your code here
145
}';
146
147
    private static $constructorMethodTemplate =
148
'public function __construct()
149
{
150
<collections>
151
}
152
';
153
154
    /**
155
     * Generate and write document classes for the given array of ClassMetadataInfo instances
156
     *
157
     * @param array $metadatas
158
     * @param string $outputDirectory
159
     * @return void
160
     */
161
    public function generate(array $metadatas, $outputDirectory)
162
    {
163
        foreach ($metadatas as $metadata) {
164
            $this->writeDocumentClass($metadata, $outputDirectory);
165
        }
166
    }
167
168
    /**
169
     * Generated and write document class to disk for the given ClassMetadataInfo instance
170
     *
171
     * @param ClassMetadataInfo $metadata
172
     * @param string $outputDirectory
173
     * @throws \RuntimeException
174
     * @return void
175
     */
176 8
    public function writeDocumentClass(ClassMetadataInfo $metadata, $outputDirectory)
177
    {
178 8
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
179 8
        $dir = dirname($path);
180
181 8
        if ( ! is_dir($dir)) {
182
            mkdir($dir, 0775, true);
183
        }
184
185 8
        $this->isNew = ! file_exists($path) || (file_exists($path) && $this->regenerateDocumentIfExists);
186
187 8
        if ( ! $this->isNew) {
188 1
            $this->parseTokensInDocumentFile($path);
189 1
        }
190
191 8
        if ($this->backupExisting && file_exists($path)) {
192 1
            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . '~';
193 1
            if ( ! copy($path, $backupPath)) {
194
                throw new \RuntimeException("Attempt to backup overwritten document file but copy operation failed.");
195
            }
196 1
        }
197
198
        // If document doesn't exist or we're re-generating the documents entirely
199 8
        if ($this->isNew) {
200 8
            file_put_contents($path, $this->generateDocumentClass($metadata));
201
202
        // If document exists and we're allowed to update the document class
203 8
        } elseif ( ! $this->isNew && $this->updateDocumentIfExists) {
204 1
            file_put_contents($path, $this->generateUpdatedDocumentClass($metadata, $path));
205 1
        }
206 8
        chmod($path, 0664);
207 8
    }
208
209
    /**
210
     * Generate a PHP5 Doctrine 2 document class from the given ClassMetadataInfo instance
211
     *
212
     * @param ClassMetadataInfo $metadata
213
     * @return string $code
214
     */
215 8
    public function generateDocumentClass(ClassMetadataInfo $metadata)
216
    {
217
        $placeHolders = array(
218 8
            '<namespace>',
219 8
            '<imports>',
220 8
            '<documentAnnotation>',
221 8
            '<documentClassName>',
222
            '<documentBody>'
223 8
        );
224
225
        $replacements = array(
226 8
            $this->generateDocumentNamespace($metadata),
227 8
            $this->generateDocumentImports($metadata),
228 8
            $this->generateDocumentDocBlock($metadata),
229 8
            $this->generateDocumentClassName($metadata),
230 8
            $this->generateDocumentBody($metadata)
231 8
        );
232
233 8
        $code = str_replace($placeHolders, $replacements, self::$classTemplate);
234 8
        return str_replace('<spaces>', $this->spaces, $code);
235
    }
236
237
    /**
238
     * Generate the updated code for the given ClassMetadataInfo and document at path
239
     *
240
     * @param ClassMetadataInfo $metadata
241
     * @param string $path
242
     * @return string $code;
243
     */
244 1
    public function generateUpdatedDocumentClass(ClassMetadataInfo $metadata, $path)
245
    {
246 1
        $currentCode = file_get_contents($path);
247
248 1
        $body = $this->generateDocumentBody($metadata);
249 1
        $body = str_replace('<spaces>', $this->spaces, $body);
250 1
        $last = strrpos($currentCode, '}');
251
252 1
        return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : '') . "}\n";
253
    }
254
255
    /**
256
     * Set the number of spaces the exported class should have
257
     *
258
     * @param integer $numSpaces
259
     * @return void
260
     */
261
    public function setNumSpaces($numSpaces)
262
    {
263
        $this->spaces = str_repeat(' ', $numSpaces);
264
        $this->numSpaces = $numSpaces;
265
    }
266
267
    /**
268
     * Set the extension to use when writing php files to disk
269
     *
270
     * @param string $extension
271
     * @return void
272
     */
273
    public function setExtension($extension)
274
    {
275
        $this->extension = $extension;
276
    }
277
278
    /**
279
     * Set the name of the class the generated classes should extend from
280
     *
281
     * @return void
282
     */
283 1
    public function setClassToExtend($classToExtend)
284
    {
285 1
        $this->classToExtend = $classToExtend;
286 1
    }
287
288
    /**
289
     * Set whether or not to generate annotations for the document
290
     *
291
     * @param bool $bool
292
     * @return void
293
     */
294 8
    public function setGenerateAnnotations($bool)
295
    {
296 8
        $this->generateAnnotations = $bool;
297 8
    }
298
299
    /**
300
     * Set whether or not to try and update the document if it already exists
301
     *
302
     * @param bool $bool
303
     * @return void
304
     */
305 8
    public function setUpdateDocumentIfExists($bool)
306
    {
307 8
        $this->updateDocumentIfExists = $bool;
308 8
    }
309
310
    /**
311
     * Set whether or not to regenerate the document if it exists
312
     *
313
     * @param bool $bool
314
     * @return void
315
     */
316 8
    public function setRegenerateDocumentIfExists($bool)
317
    {
318 8
        $this->regenerateDocumentIfExists = $bool;
319 8
    }
320
321
    /**
322
     * Set whether or not to generate stub methods for the document
323
     *
324
     * @param bool $bool
325
     * @return void
326
     */
327 8
    public function setGenerateStubMethods($bool)
328
    {
329 8
        $this->generateDocumentStubMethods = $bool;
330 8
    }
331
332
    /**
333
     * Should an existing document be backed up if it already exists?
334
     */
335
    public function setBackupExisting($bool)
336
    {
337
        $this->backupExisting = $bool;
338
    }
339
340 8
    private function generateDocumentNamespace(ClassMetadataInfo $metadata)
341
    {
342 8
        if ($this->hasNamespace($metadata)) {
343 8
            return 'namespace ' . $this->getNamespace($metadata) . ';';
344
        }
345
    }
346
347 8
    private function generateDocumentClassName(ClassMetadataInfo $metadata)
348
    {
349 8
        return 'class ' . $this->getClassName($metadata) .
350 8
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
351
    }
352
353 8
    private function generateDocumentBody(ClassMetadataInfo $metadata)
354
    {
355 8
        $fieldMappingProperties = $this->generateDocumentFieldMappingProperties($metadata);
356 8
        $associationMappingProperties = $this->generateDocumentAssociationMappingProperties($metadata);
357 8
        $stubMethods = $this->generateDocumentStubMethods ? $this->generateDocumentStubMethods($metadata) : null;
358 8
        $lifecycleCallbackMethods = $this->generateDocumentLifecycleCallbackMethods($metadata);
359
360 8
        $code = array();
361
362 8
        if ($fieldMappingProperties) {
363 7
            $code[] = $fieldMappingProperties;
364 7
        }
365
366 8
        if ($associationMappingProperties) {
367 6
            $code[] = $associationMappingProperties;
368 6
        }
369
370 8
        $code[] = $this->generateDocumentConstructor($metadata);
371
372 8
        if ($stubMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stubMethods of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
373 8
            $code[] = $stubMethods;
374 8
        }
375
376 8
        if ($lifecycleCallbackMethods) {
377 6
            $code[] = "\n" . $lifecycleCallbackMethods;
378 6
        }
379
380 8
        return implode("\n", $code);
381
    }
382
383 8
    private function generateDocumentConstructor(ClassMetadataInfo $metadata)
384
    {
385 8
        if ($this->hasMethod('__construct', $metadata)) {
386 1
            return '';
387
        }
388
389 8
        $collections = array();
390 8
        foreach ($metadata->fieldMappings AS $mapping) {
391 8
            if ($mapping['type'] === ClassMetadataInfo::MANY) {
392 6
                $collections[] = '$this->' . $mapping['fieldName'] . ' = new \Doctrine\Common\Collections\ArrayCollection();';
393 6
            }
394 8
        }
395 8
        if ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
396 6
            return $this->prefixCodeWithSpaces(str_replace("<collections>", $this->spaces . implode("\n" . $this->spaces, $collections), self::$constructorMethodTemplate));
397
        }
398 2
        return '';
399
    }
400
401
    /**
402
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
403
     * @param string $path
404
     */
405 1
    private function parseTokensInDocumentFile($path)
406
    {
407 1
        $tokens = token_get_all(file_get_contents($path));
408 1
        $lastSeenNamespace = '';
409 1
        $lastSeenClass = false;
410
411 1
        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...
412 1
            $token = $tokens[$i];
413 1
            if ($token[0] == T_NAMESPACE) {
414 1
                $peek = $i;
415 1
                $lastSeenNamespace = '';
416 1
                while (isset($tokens[++$peek])) {
417 1
                    if (';' == $tokens[$peek]) {
418 1
                        break;
419 1
                    } elseif (is_array($tokens[$peek]) && in_array($tokens[$peek][0], array(T_STRING, T_NS_SEPARATOR))) {
420 1
                        $lastSeenNamespace .= $tokens[$peek][1];
421 1
                    }
422 1
                }
423 1
            } elseif ($token[0] == T_CLASS) {
424 1
                $lastSeenClass = $lastSeenNamespace . '\\' . $tokens[$i + 2][1];
425 1
                $this->staticReflection[$lastSeenClass]['properties'] = array();
426 1
                $this->staticReflection[$lastSeenClass]['methods'] = array();
427 1
            } elseif ($token[0] == T_FUNCTION) {
428 1
                if ($tokens[$i + 2][0] == T_STRING) {
429 1
                    $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i + 2][1];
430 1
                } elseif ($tokens[$i + 2][0] == '&' && $tokens[$i + 3][0] == T_STRING) {
431
                    $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i + 3][1];
432
                }
433 1
            } elseif (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i + 2][0] != T_FUNCTION) {
434 1
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i + 2][1], 1);
435 1
            }
436 1
        }
437 1
    }
438
439 8 View Code Duplication
    private function hasProperty($property, ClassMetadataInfo $metadata)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
440
    {
441 8
        if ($this->extendsClass()) {
442
            // don't generate property if its already on the base class.
443 1
            $reflClass = new \ReflectionClass($this->getClassToExtend());
444 1
            if ($reflClass->hasProperty($property)) {
445
                return true;
446
            }
447 1
        }
448
        
449 8
        foreach ($this->getTraits($metadata) as $trait) {
450 2
            if ($trait->hasProperty($property)) {
451 2
                return true;
452
            }
453 8
        }
454
        
455
        return (
456 8
            isset($this->staticReflection[$metadata->name]) &&
457 1
            in_array($property, $this->staticReflection[$metadata->name]['properties'])
458 8
        );
459
    }
460
461 8 View Code Duplication
    private function hasMethod($method, ClassMetadataInfo $metadata)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
462
    {
463 8
        if ($this->extendsClass()) {
464
            // don't generate method if its already on the base class.
465 1
            $reflClass = new \ReflectionClass($this->getClassToExtend());
466 1
            if ($reflClass->hasMethod($method)) {
467
                return true;
468
            }
469 1
        }
470
        
471 8
        foreach ($this->getTraits($metadata) as $trait) {
472 2
            if ($trait->hasMethod($method)) {
473 2
                return true;
474
            }
475 8
        }
476
        
477
        return (
478 8
            isset($this->staticReflection[$metadata->name]) &&
479 1
            in_array($method, $this->staticReflection[$metadata->name]['methods'])
480 8
        );
481
    }
482
483 8
    private function hasNamespace(ClassMetadataInfo $metadata)
484
    {
485 8
        return strpos($metadata->name, '\\') ? true : false;
486
    }
487
488 8
    private function extendsClass()
489
    {
490 8
        return $this->classToExtend ? true : false;
491
    }
492
493 1
    private function getClassToExtend()
494
    {
495 1
        return $this->classToExtend;
496
    }
497
498 1
    private function getClassToExtendName()
499
    {
500 1
        $refl = new \ReflectionClass($this->getClassToExtend());
501
502 1
        return '\\' . $refl->getName();
503
    }
504
505 8
    private function getClassName(ClassMetadataInfo $metadata)
506
    {
507 8
        return ($pos = strrpos($metadata->name, '\\'))
508 8
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
509
    }
510
511 8
    private function getNamespace(ClassMetadataInfo $metadata)
512
    {
513 8
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
514
    }
515
    
516
    /**
517
     * @param ClassMetadataInfo $metadata
518
     *
519
     * @return array
520
     */
521 8
    protected function getTraits(ClassMetadataInfo $metadata) 
522
    {
523 8
        if (PHP_VERSION_ID >= 50400 && ($metadata->reflClass !== null || class_exists($metadata->name))) {
524 2
            $reflClass = $metadata->reflClass === null ? new \ReflectionClass($metadata->name) : $metadata->reflClass;
525 2
            $traits = array();
526 2
            while ($reflClass !== false) {
527 2
                $traits = array_merge($traits, $reflClass->getTraits());
528 2
                $reflClass = $reflClass->getParentClass();
529 2
            }
530 2
            return $traits;
531
        }
532 6
        return array();
533
    }
534
535 8
    private function generateDocumentImports(ClassMetadataInfo $metadata)
0 ignored issues
show
Unused Code introduced by
The parameter $metadata 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...
536
    {
537 8
        if ($this->generateAnnotations) {
538 8
            return 'use Doctrine\\ODM\\MongoDB\\Mapping\\Annotations as ODM;';
539
        }
540
    }
541
542 8
    private function generateDocumentDocBlock(ClassMetadataInfo $metadata)
543
    {
544 8
        $lines = array();
545 8
        $lines[] = '/**';
546 8
        $lines[] = ' * ' . $metadata->name;
547
548 8
        if ($this->generateAnnotations) {
549 8
            $lines[] = ' *';
550
551 8
            if ($metadata->isMappedSuperclass) {
552
                $lines[] = ' * @ODM\\MappedSuperclass';
553 8
            } elseif ($metadata->isEmbeddedDocument) {
554
                $lines[] = ' * @ODM\\EmbeddedDocument';
555
            } else {
556 8
                $lines[] = ' * @ODM\\Document';
557
            }
558
559 8
            $document = array();
560 8
            if ( ! $metadata->isMappedSuperclass && ! $metadata->isEmbeddedDocument) {
561 8
                if ($metadata->collection) {
562 8
                    $document[] = ' *     collection="' . $metadata->collection . '"';
563 8
                }
564 8
                if ($metadata->customRepositoryClassName) {
565 6
                    $document[] = ' *     repositoryClass="' . $metadata->customRepositoryClassName . '"';
566 6
                }
567 8
            }
568 8
            if ($metadata->indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
569
                $indexes = array();
570
                $indexLines = array();
571
                $indexLines[] = " *     indexes={";
572
                foreach ($metadata->indexes as $index) {
573
                    $keys = array();
574 View Code Duplication
                    foreach ($index['keys'] as $key => $value) {
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...
575
                        $keys[] = '"' . $key . '"="' . $value . '"';
576
                    }
577
                    $options = array();
578 View Code Duplication
                    foreach ($index['options'] as $key => $value) {
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...
579
                        $options[] = '"' . $key . '"="' . $value . '"';
580
                    }
581
                    $indexes[] = '@ODM\\Index(keys={' . implode(', ', $keys) . '}, options={' . implode(', ', $options) . '})';
582
                }
583
                $indexLines[] = "\n *         " . implode(",\n *         ", $indexes);
584
                $indexLines[] = "\n *     }";
585
586
                $document[] = implode(null, $indexLines);
587
            }
588
589 8
            if ($document) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $document of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
590 8
                $lines[count($lines) - 1] .= '(';
591 8
                $lines[] = implode(",\n", $document);
592 8
                $lines[] = ' * )';
593 8
            }
594
595 8
            if ( ! empty($metadata->lifecycleCallbacks)) {
596 6
                $lines[] = ' * @ODM\HasLifecycleCallbacks';
597 6
            }
598
599
            $methods = array(
600 8
                'generateInheritanceAnnotation',
601 8
                'generateDiscriminatorFieldAnnotation',
602 8
                'generateDiscriminatorMapAnnotation',
603 8
                'generateDefaultDiscriminatorValueAnnotation',
604
                'generateChangeTrackingPolicyAnnotation'
605 8
            );
606
607 8
            foreach ($methods as $method) {
608 8
                if ($code = $this->$method($metadata)) {
609 8
                    $lines[] = ' * ' . $code;
610 8
                }
611 8
            }
612 8
        }
613
614 8
        $lines[] = ' */';
615 8
        return implode("\n", $lines);
616
    }
617
618 8
    private function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
619
    {
620 8
        if ($metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
621
            return '@ODM\\InheritanceType("' . $this->getInheritanceTypeString($metadata->inheritanceType) . '")';
622
        }
623 8
    }
624
625 8
    private function generateDiscriminatorFieldAnnotation(ClassMetadataInfo $metadata)
626
    {
627 8
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
628
            return '@ODM\\DiscriminatorField(name="' . $metadata->discriminatorField . '")';
629
        }
630 8
    }
631
632 8
    private function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
633
    {
634 8
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
635
            $inheritanceClassMap = array();
636
637
            foreach ($metadata->discriminatorMap as $type => $class) {
638
                $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
639
            }
640
641
            return '@ODM\\DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
642
        }
643 8
    }
644
645 8
    private function generateDefaultDiscriminatorValueAnnotation(ClassMetadataInfo $metadata)
646
    {
647 8
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION && isset($metadata->defaultDiscriminatorValue)) {
648
            return '@ODM\\DefaultDiscriminatorValue("' . $metadata->defaultDiscriminatorValue . '")';
649
        }
650 8
    }
651
652 8
    private function generateChangeTrackingPolicyAnnotation(ClassMetadataInfo $metadata)
653
    {
654 8
        return '@ODM\\ChangeTrackingPolicy("' . $this->getChangeTrackingPolicyString($metadata->changeTrackingPolicy) . '")';
655
    }
656
657 8
    private function generateDocumentStubMethods(ClassMetadataInfo $metadata)
658
    {
659 8
        $methods = array();
660
661 8
        foreach ($metadata->fieldMappings as $fieldMapping) {
662 8
            if (isset($fieldMapping['id'])) {
663 8
                if ($metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) {
664
                    if ($code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
665
                        $methods[] = $code;
666
                    }
667
                }
668 8
                if ($code = $code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
669 8
                    $methods[] = $code;
670 8
                }
671 8
            } elseif ( ! isset($fieldMapping['association'])) {
672 8
                if ($code = $code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
673 8
                    $methods[] = $code;
674 8
                }
675 8
                if ($code = $code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
676 8
                    $methods[] = $code;
677 8
                }
678 8
            } elseif ($fieldMapping['type'] === ClassMetadataInfo::ONE) {
679 8
                $nullable = $this->isAssociationNullable($fieldMapping) ? 'null' : null;
680 8 View Code Duplication
                if ($code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null, $nullable)) {
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...
681 6
                    $methods[] = $code;
682 6
                }
683 8 View Code Duplication
                if ($code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null)) {
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...
684 6
                    $methods[] = $code;
685 6
                }
686 8
            } elseif ($fieldMapping['type'] === ClassMetadataInfo::MANY) {
687 6 View Code Duplication
                if ($code = $this->generateDocumentStubMethod($metadata, 'add', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null)) {
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...
688 6
                    $methods[] = $code;
689 6
                }
690 6 View Code Duplication
                if ($code = $this->generateDocumentStubMethod($metadata, 'remove', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null)) {
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...
691 6
                    $methods[] = $code;
692 6
                }
693 6
                if ($code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], '\Doctrine\Common\Collections\Collection')) {
694 6
                    $methods[] = $code;
695 6
                }
696 6
            }
697 8
        }
698
699 8
        return implode("\n\n", $methods);
700
    }
701
    
702
    /**
703
     * @param array $fieldMapping
704
     *
705
     * @return bool
706
     */
707 8
    protected function isAssociationNullable($fieldMapping)
708
    {
709 8
        return isset($fieldMapping['nullable']) && $fieldMapping['nullable'];
710
    }
711
712 8
    private function generateDocumentLifecycleCallbackMethods(ClassMetadataInfo $metadata)
713
    {
714 8
        if (empty($metadata->lifecycleCallbacks)) {
715 2
            return '';
716
        }
717
718 6
        $methods = array();
719
720 6
        foreach ($metadata->lifecycleCallbacks as $event => $callbacks) {
721 6
            foreach ($callbacks as $callback) {
722 6
                if ($code = $this->generateLifecycleCallbackMethod($event, $callback, $metadata)) {
723 6
                    $methods[] = $code;
724 6
                }
725 6
            }
726 6
        }
727
728 6
        return implode("\n\n", $methods);
729
    }
730
731 8
    private function generateDocumentAssociationMappingProperties(ClassMetadataInfo $metadata)
732
    {
733 8
        $lines = array();
734
735 8
        foreach ($metadata->fieldMappings as $fieldMapping) {
736 8
            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
737 8
                $metadata->isInheritedField($fieldMapping['fieldName'])) {
738 3
                continue;
739
            }
740 7
            if ( ! isset($fieldMapping['association'])) {
741 7
                continue;
742
            }
743
744 6
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($fieldMapping, $metadata);
745 6
            $lines[] = $this->spaces . 'protected $' . $fieldMapping['fieldName']
746 6
                . ($fieldMapping['type'] === ClassMetadataInfo::MANY ? ' = array()' : null) . ";\n";
747 8
        }
748
749 8
        return implode("\n", $lines);
750
    }
751
752 8
    private function generateDocumentFieldMappingProperties(ClassMetadataInfo $metadata)
753
    {
754 8
        $lines = array();
755
756 8
        foreach ($metadata->fieldMappings as $fieldMapping) {
757 8
            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
758 8
                $metadata->isInheritedField($fieldMapping['fieldName'])) {
759 3
                continue;
760
            }
761 7
            if (isset($fieldMapping['association']) && $fieldMapping['association']) {
762 6
                continue;
763
            }
764
765 7
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
766 7
            $lines[] = $this->spaces . 'protected $' . $fieldMapping['fieldName']
767 7
                . (isset($fieldMapping['default']) ? ' = ' . var_export($fieldMapping['default'], true) : null) . ";\n";
768 8
        }
769
770 8
        return implode("\n", $lines);
771
    }
772
773 8
    private function generateDocumentStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
774
    {
775
        // Add/remove methods should use the singular form of the field name
776 8
        $formattedFieldName = in_array($type, array('add', 'remove'))
777 8
            ? Inflector::singularize($fieldName)
778 8
            : $fieldName;
779
780 8
        $methodName = $type . Inflector::classify($formattedFieldName);
781 8
        $variableName = Inflector::camelize($formattedFieldName);
782
783 8
        if ($this->hasMethod($methodName, $metadata)) {
784 3
            return;
785
        }
786
787 8
        $description = ucfirst($type) . ' ' . $variableName;
788
789 8
        $types = Type::getTypesMap();
790 8
        $methodTypeHint = $typeHint && ! isset($types[$typeHint]) ? '\\' . $typeHint . ' ' : null;
791 8
        $variableType = $typeHint ? $typeHint . ' ' : null;
792
793
        $replacements = array(
794 8
            '<description>'         => $description,
795 8
            '<methodTypeHint>'      => $methodTypeHint,
796 8
            '<variableType>'        => $variableType,
797 8
            '<variableName>'        => $variableName,
798 8
            '<methodName>'          => $methodName,
799 8
            '<fieldName>'           => $fieldName,
800 8
            '<variableDefault>'     => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
801 8
        );
802
803 8
        $templateVar = sprintf('%sMethodTemplate', $type);
804
805 8
        $method = str_replace(
806 8
            array_keys($replacements),
807 8
            array_values($replacements),
808
            self::$$templateVar
809 8
        );
810
811 8
        return $this->prefixCodeWithSpaces($method);
812
    }
813
814 6
    private function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
815
    {
816 6
        if ($this->hasMethod($methodName, $metadata)) {
817 1
            return;
818
        }
819
820
        $replacements = array(
821 6
            '<comment>'    => $this->generateAnnotations ? '/** @ODM\\' . ucfirst($name) . ' */' : '',
822 6
            '<methodName>' => $methodName,
823 6
        );
824
825 6
        $method = str_replace(
826 6
            array_keys($replacements),
827 6
            array_values($replacements),
828
            self::$lifecycleCallbackMethodTemplate
829 6
        );
830
831 6
        return $this->prefixCodeWithSpaces($method);
832
    }
833
834 6
    private function generateAssociationMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
0 ignored issues
show
Unused Code introduced by
The parameter $metadata 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...
835
    {
836 6
        $lines = array();
837 6
        $lines[] = $this->spaces . '/**';
838 6
        $lines[] = $this->spaces . ' * @var ' . (isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : 'object');
839
840 6
        if ($this->generateAnnotations) {
841 6
            $lines[] = $this->spaces . ' *';
842
843 6
            $type = null;
844 6
            switch ($fieldMapping['association']) {
845 6
                case ClassMetadataInfo::EMBED_ONE:
846
                    $type = 'EmbedOne';
847
                    break;
848 6
                case ClassMetadataInfo::EMBED_MANY:
849
                    $type = 'EmbedMany';
850
                    break;
851 6
                case ClassMetadataInfo::REFERENCE_ONE:
852 6
                    $type = 'ReferenceOne';
853 6
                    break;
854 6
                case ClassMetadataInfo::REFERENCE_MANY:
855 6
                    $type = 'ReferenceMany';
856 6
                    break;
857 6
            }
858 6
            $typeOptions = array();
859
860 6
            if (isset($fieldMapping['targetDocument'])) {
861 6
                $typeOptions[] = 'targetDocument="' . $fieldMapping['targetDocument'] . '"';
862 6
            }
863
864 6
            if (isset($fieldMapping['cascade']) && $fieldMapping['cascade']) {
865
                $cascades = array();
866
867
                if ($fieldMapping['isCascadePersist']) $cascades[] = '"persist"';
868
                if ($fieldMapping['isCascadeRemove']) $cascades[] = '"remove"';
869
                if ($fieldMapping['isCascadeDetach']) $cascades[] = '"detach"';
870
                if ($fieldMapping['isCascadeMerge']) $cascades[] = '"merge"';
871
                if ($fieldMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
872
873
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
874
            }
875
876 6
            $lines[] = $this->spaces . ' * @ODM\\' . $type . '(' . implode(', ', $typeOptions) . ')';
877 6
        }
878
879 6
        $lines[] = $this->spaces . ' */';
880
881 6
        return implode("\n", $lines);
882
    }
883
884 7
    private function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
885
    {
886 7
        $lines = array();
887 7
        $lines[] = $this->spaces . '/**';
888 7
        if (isset($fieldMapping['id']) && $fieldMapping['id']) {
889 7
            $fieldMapping['strategy'] = isset($fieldMapping['strategy']) ? $fieldMapping['strategy'] : ClassMetadataInfo::GENERATOR_TYPE_AUTO;
890 7
            if ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_AUTO) {
891 6
                $lines[] = $this->spaces . ' * @var MongoId $' . $fieldMapping['fieldName'];
892 7 View Code Duplication
            } elseif ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_INCREMENT) {
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...
893
                $lines[] = $this->spaces . ' * @var integer $' . $fieldMapping['fieldName'];
894 1
            } elseif ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_UUID) {
895
                $lines[] = $this->spaces . ' * @var string $' . $fieldMapping['fieldName'];
896 1 View Code Duplication
            } elseif ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_NONE) {
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...
897
                $lines[] = $this->spaces . ' * @var $' . $fieldMapping['fieldName'];
898
            } else {
899 1
                $lines[] = $this->spaces . ' * @var $' . $fieldMapping['fieldName'];
900
            }
901 7
        } else {
902 7
            $lines[] = $this->spaces . ' * @var ' . $fieldMapping['type'] . ' $' . $fieldMapping['fieldName'];
903
        }
904
905 7
        if ($this->generateAnnotations) {
906 7
            $lines[] = $this->spaces . ' *';
907
908 7
            $field = array();
909 7
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
910 7
                if (isset($fieldMapping['strategy'])) {
911 7
                    $field[] = 'strategy="' . $this->getIdGeneratorTypeString($metadata->generatorType) . '"';
912 7
                }
913 7
                $lines[] = $this->spaces . ' * @ODM\\Id(' . implode(', ', $field) . ')';
914 7
            } else {
915 7
                if (isset($fieldMapping['name'])) {
916 7
                    $field[] = 'name="' . $fieldMapping['name'] . '"';
917 7
                }
918
919 7
                if (isset($fieldMapping['type'])) {
920 7
                    $field[] = 'type="' . $fieldMapping['type'] . '"';
921 7
                }
922
923 7
                if (isset($fieldMapping['nullable']) && $fieldMapping['nullable'] === true) {
924
                    $field[] = 'nullable=' . var_export($fieldMapping['nullable'], true);
925
                }
926 7
                if (isset($fieldMapping['options'])) {
927 1
                    $options = array();
928 1
                    foreach ($fieldMapping['options'] as $key => $value) {
929
                        $options[] = '"' . $key . '" = "' . $value . '"';
930 1
                    }
931 1
                    $field[] = "options={" . implode(', ', $options) . "}";
932 1
                }
933 7
                $lines[] = $this->spaces . ' * @ODM\\Field(' . implode(', ', $field) . ')';
934
            }
935
936 7
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
937
                $lines[] = $this->spaces . ' * @ODM\\Version';
938
            }
939 7
        }
940
941 7
        $lines[] = $this->spaces . ' */';
942
943 7
        return implode("\n", $lines);
944
    }
945
946 8
    private function prefixCodeWithSpaces($code, $num = 1)
947
    {
948 8
        $lines = explode("\n", $code);
949
950 8
        foreach ($lines as $key => $value) {
951 8
            $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
952 8
        }
953
954 8
        return implode("\n", $lines);
955
    }
956
957
    private function getInheritanceTypeString($type)
958
    {
959
        switch ($type) {
960
            case ClassMetadataInfo::INHERITANCE_TYPE_NONE:
961
                return 'NONE';
962
963
            case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION:
964
                return 'SINGLE_COLLECTION';
965
966
            case ClassMetadataInfo::INHERITANCE_TYPE_COLLECTION_PER_CLASS:
967
                return 'COLLECTION_PER_CLASS';
968
969
            default:
970
                throw new \InvalidArgumentException('Invalid provided InheritanceType: ' . $type);
971
        }
972
    }
973
974 8
    private function getChangeTrackingPolicyString($policy)
975
    {
976
        switch ($policy) {
977 8
            case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT:
978 8
                return 'DEFERRED_IMPLICIT';
979
980
            case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT:
981
                return 'DEFERRED_EXPLICIT';
982
983
            case ClassMetadataInfo::CHANGETRACKING_NOTIFY:
984
                return 'NOTIFY';
985
986
            default:
987
                throw new \InvalidArgumentException('Invalid provided ChangeTrackingPolicy: ' . $policy);
988
        }
989
    }
990
991 7
    private function getIdGeneratorTypeString($type)
992
    {
993
        switch ($type) {
994 7
            case ClassMetadataInfo::GENERATOR_TYPE_AUTO:
995 1
                return 'AUTO';
996
997 6
            case ClassMetadataInfo::GENERATOR_TYPE_INCREMENT:
998
                return 'INCREMENT';
999
1000 6
            case ClassMetadataInfo::GENERATOR_TYPE_UUID:
1001
                return 'UUID';
1002
                
1003 6
            case ClassMetadataInfo::GENERATOR_TYPE_ALNUM:
1004
                return 'ALNUM';
1005
1006 6
            case ClassMetadataInfo::GENERATOR_TYPE_CUSTOM:
1007 6
                return 'CUSTOM';
1008
                
1009
            case ClassMetadataInfo::GENERATOR_TYPE_NONE:
1010
                return 'NONE';
1011
1012
            default:
1013
                throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
1014
        }
1015
    }
1016
}
1017