Completed
Pull Request — 1.0.x (#1416)
by Maciej
09:32
created

DocumentGenerator::getClassToExtend()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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 7
    public function writeDocumentClass(ClassMetadataInfo $metadata, $outputDirectory)
177
    {
178 7
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
179 7
        $dir = dirname($path);
180
181 7
        if ( ! is_dir($dir)) {
182
            mkdir($dir, 0775, true);
183
        }
184
185 7
        $this->isNew = ! file_exists($path) || (file_exists($path) && $this->regenerateDocumentIfExists);
186
187 7
        if ( ! $this->isNew) {
188 2
            $this->parseTokensInDocumentFile($path);
189 2
        }
190
191 7
        if ($this->backupExisting && file_exists($path)) {
192 2
            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . '~';
193 2
            if ( ! copy($path, $backupPath)) {
194
                throw new \RuntimeException("Attempt to backup overwritten document file but copy operation failed.");
195
            }
196 2
        }
197
198
        // If document doesn't exist or we're re-generating the documents entirely
199 7
        if ($this->isNew) {
200 6
            file_put_contents($path, $this->generateDocumentClass($metadata));
201
202
        // If document exists and we're allowed to update the document class
203 7
        } elseif ( ! $this->isNew && $this->updateDocumentIfExists) {
204 2
            file_put_contents($path, $this->generateUpdatedDocumentClass($metadata, $path));
205 2
        }
206 7
        chmod($path, 0664);
207 7
    }
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 6
    public function generateDocumentClass(ClassMetadataInfo $metadata)
216
    {
217
        $placeHolders = array(
218 6
            '<namespace>',
219 6
            '<imports>',
220 6
            '<documentAnnotation>',
221 6
            '<documentClassName>',
222
            '<documentBody>'
223 6
        );
224
225
        $replacements = array(
226 6
            $this->generateDocumentNamespace($metadata),
227 6
            $this->generateDocumentImports($metadata),
0 ignored issues
show
Unused Code introduced by
The call to DocumentGenerator::generateDocumentImports() has too many arguments starting with $metadata.

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...
228 6
            $this->generateDocumentDocBlock($metadata),
229 6
            $this->generateDocumentClassName($metadata),
230 6
            $this->generateDocumentBody($metadata)
231 6
        );
232
233 6
        $code = str_replace($placeHolders, $replacements, self::$classTemplate);
234 6
        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 2
    public function generateUpdatedDocumentClass(ClassMetadataInfo $metadata, $path)
245
    {
246 2
        $currentCode = file_get_contents($path);
247
248 2
        $body = $this->generateDocumentBody($metadata);
249 2
        $body = str_replace('<spaces>', $this->spaces, $body);
250 2
        $last = strrpos($currentCode, '}');
251
252 2
        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 7
    public function setGenerateAnnotations($bool)
295
    {
296 7
        $this->generateAnnotations = $bool;
297 7
    }
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 7
    public function setUpdateDocumentIfExists($bool)
306
    {
307 7
        $this->updateDocumentIfExists = $bool;
308 7
    }
309
310
    /**
311
     * Set whether or not to regenerate the document if it exists
312
     *
313
     * @param bool $bool
314
     * @return void
315
     */
316 7
    public function setRegenerateDocumentIfExists($bool)
317
    {
318 7
        $this->regenerateDocumentIfExists = $bool;
319 7
    }
320
321
    /**
322
     * Set whether or not to generate stub methods for the document
323
     *
324
     * @param bool $bool
325
     * @return void
326
     */
327 7
    public function setGenerateStubMethods($bool)
328
    {
329 7
        $this->generateDocumentStubMethods = $bool;
330 7
    }
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 6
    private function generateDocumentNamespace(ClassMetadataInfo $metadata)
341
    {
342 6
        if ($this->hasNamespace($metadata)) {
343 6
            return 'namespace ' . $this->getNamespace($metadata) . ';';
344
        }
345
    }
346
347 6
    private function generateDocumentClassName(ClassMetadataInfo $metadata)
348
    {
349 6
        return 'class ' . $this->getClassName($metadata) .
350 6
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
351
    }
352
353 7
    private function generateDocumentBody(ClassMetadataInfo $metadata)
354
    {
355 7
        $fieldMappingProperties = $this->generateDocumentFieldMappingProperties($metadata);
356 7
        $associationMappingProperties = $this->generateDocumentAssociationMappingProperties($metadata);
357 7
        $stubMethods = $this->generateDocumentStubMethods ? $this->generateDocumentStubMethods($metadata) : null;
358 7
        $lifecycleCallbackMethods = $this->generateDocumentLifecycleCallbackMethods($metadata);
359
360 7
        $code = array();
361
362 7
        if ($fieldMappingProperties) {
363 6
            $code[] = $fieldMappingProperties;
364 6
        }
365
366 7
        if ($associationMappingProperties) {
367 6
            $code[] = $associationMappingProperties;
368 6
        }
369
370 7
        $code[] = $this->generateDocumentConstructor($metadata);
371
372 7
        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 7
            $code[] = $stubMethods;
374 7
        }
375
376 7
        if ($lifecycleCallbackMethods) {
377 6
            $code[] = "\n" . $lifecycleCallbackMethods;
378 6
        }
379
380 7
        return implode("\n", $code);
381
    }
382
383 7
    private function generateDocumentConstructor(ClassMetadataInfo $metadata)
384
    {
385 7
        if ($this->hasMethod('__construct', $metadata)) {
386 1
            return '';
387
        }
388
389 7
        $collections = array();
390 7
        foreach ($metadata->fieldMappings AS $mapping) {
391 7
            if ($mapping['type'] === ClassMetadataInfo::MANY) {
392 6
                $collections[] = '$this->' . $mapping['fieldName'] . ' = new \Doctrine\Common\Collections\ArrayCollection();';
393 6
            }
394 7
        }
395 7
        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 1
        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 2
    private function parseTokensInDocumentFile($path)
406
    {
407 2
        $tokens = token_get_all(file_get_contents($path));
408 2
        $lastSeenNamespace = '';
409 2
        $lastSeenClass = false;
410
411 2
        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 2
            $token = $tokens[$i];
413 2
            if ($token[0] == T_NAMESPACE) {
414 2
                $peek = $i;
415 2
                $lastSeenNamespace = '';
416 2
                while (isset($tokens[++$peek])) {
417 2
                    if (';' == $tokens[$peek]) {
418 2
                        break;
419 2
                    } elseif (is_array($tokens[$peek]) && in_array($tokens[$peek][0], array(T_STRING, T_NS_SEPARATOR))) {
420 2
                        $lastSeenNamespace .= $tokens[$peek][1];
421 2
                    }
422 2
                }
423 2
            } elseif ($token[0] == T_CLASS) {
424 2
                $lastSeenClass = $lastSeenNamespace . '\\' . $tokens[$i + 2][1];
425 2
                $this->staticReflection[$lastSeenClass]['properties'] = array();
426 2
                $this->staticReflection[$lastSeenClass]['methods'] = array();
427 2
            } 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 2
            } elseif (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i + 2][0] != T_FUNCTION) {
434 2
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i + 2][1], 1);
435 2
            }
436 2
        }
437 2
    }
438
439 7 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 7
        if ($this->extendsClass() || class_exists($metadata->name)) {
442
            // don't generate property if its already on the base class.
443 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
444
445 2
            if ($reflClass->hasProperty($property)) {
446 1
                return true;
447
            }
448 1
        }
449
450 6
        foreach ($this->getTraits($metadata) as $trait) {
451
            if ($trait->hasProperty($property)) {
452
                return true;
453
            }
454 6
        }
455
456
        return (
457 6
            isset($this->staticReflection[$metadata->name]) &&
458 1
            in_array($property, $this->staticReflection[$metadata->name]['properties'])
459 6
        );
460
    }
461
462 7 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...
463
    {
464 7
        if ($this->extendsClass() || class_exists($metadata->name)) {
465
            // don't generate method if its already on the base class.
466 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
467
468 2
            if ($reflClass->hasMethod($method)) {
469 1
                return true;
470
            }
471 2
        }
472
473 7
        foreach ($this->getTraits($metadata) as $trait) {
474
            if ($trait->hasMethod($method)) {
475
                return true;
476
            }
477 7
        }
478
479
        return (
480 7
            isset($this->staticReflection[$metadata->name]) &&
481 2
            in_array($method, $this->staticReflection[$metadata->name]['methods'])
482 7
        );
483
    }
484
485 6
    private function hasNamespace(ClassMetadataInfo $metadata)
486
    {
487 6
        return strpos($metadata->name, '\\') ? true : false;
488
    }
489
490 7
    private function extendsClass()
491
    {
492 7
        return $this->classToExtend ? true : false;
493
    }
494
495 2
    private function getClassToExtend()
496
    {
497 2
        return $this->classToExtend;
498
    }
499
500 1
    private function getClassToExtendName()
501
    {
502 1
        $refl = new \ReflectionClass($this->getClassToExtend());
503
504 1
        return '\\' . $refl->getName();
505
    }
506
507 6
    private function getClassName(ClassMetadataInfo $metadata)
508
    {
509 6
        return ($pos = strrpos($metadata->name, '\\'))
510 6
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
511
    }
512
513 6
    private function getNamespace(ClassMetadataInfo $metadata)
514
    {
515 6
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
516
    }
517
518
    /**
519
     * @param ClassMetadataInfo $metadata
520
     *
521
     * @return array
522
     */
523 7
    protected function getTraits(ClassMetadataInfo $metadata)
524
    {
525 7
        if (PHP_VERSION_ID >= 50400 && ($metadata->reflClass !== null || class_exists($metadata->name))) {
526
            $reflClass = $metadata->reflClass === null ? new \ReflectionClass($metadata->name) : $metadata->reflClass;
527
            $traits = array();
528
            while ($reflClass !== false) {
529
                $traits = array_merge($traits, $reflClass->getTraits());
530
                $reflClass = $reflClass->getParentClass();
531
            }
532
            return $traits;
533
        }
534 7
        return array();
535
    }
536
537 6
    private function generateDocumentImports()
538
    {
539 6
        if ($this->generateAnnotations) {
540 6
            return 'use Doctrine\\ODM\\MongoDB\\Mapping\\Annotations as ODM;';
541
        }
542
    }
543
544 6
    private function generateDocumentDocBlock(ClassMetadataInfo $metadata)
545
    {
546 6
        $lines = array();
547 6
        $lines[] = '/**';
548 6
        $lines[] = ' * ' . $metadata->name;
549
550 6
        if ($this->generateAnnotations) {
551 6
            $lines[] = ' *';
552
553 6
            if ($metadata->isMappedSuperclass) {
554
                $lines[] = ' * @ODM\\MappedSuperclass';
555 6
            } elseif ($metadata->isEmbeddedDocument) {
556
                $lines[] = ' * @ODM\\EmbeddedDocument';
557
            } else {
558 6
                $lines[] = ' * @ODM\\Document';
559
            }
560
561 6
            $document = array();
562 6
            if ( ! $metadata->isMappedSuperclass && ! $metadata->isEmbeddedDocument) {
563 6
                if ($metadata->collection) {
564 6
                    $document[] = ' *     collection="' . $metadata->collection . '"';
565 6
                }
566 6
                if ($metadata->customRepositoryClassName) {
567 6
                    $document[] = ' *     repositoryClass="' . $metadata->customRepositoryClassName . '"';
568 6
                }
569 6
            }
570 6
            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...
571
                $indexes = array();
572
                $indexLines = array();
573
                $indexLines[] = " *     indexes={";
574
                foreach ($metadata->indexes as $index) {
575
                    $keys = array();
576 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...
577
                        $keys[] = '"' . $key . '"="' . $value . '"';
578
                    }
579
                    $options = array();
580 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...
581
                        $options[] = '"' . $key . '"="' . $value . '"';
582
                    }
583
                    $indexes[] = '@ODM\\Index(keys={' . implode(', ', $keys) . '}, options={' . implode(', ', $options) . '})';
584
                }
585
                $indexLines[] = "\n *         " . implode(",\n *         ", $indexes);
586
                $indexLines[] = "\n *     }";
587
588
                $document[] = implode(null, $indexLines);
589
            }
590
591 6
            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...
592 6
                $lines[count($lines) - 1] .= '(';
593 6
                $lines[] = implode(",\n", $document);
594 6
                $lines[] = ' * )';
595 6
            }
596
597 6
            if ( ! empty($metadata->lifecycleCallbacks)) {
598 6
                $lines[] = ' * @ODM\HasLifecycleCallbacks';
599 6
            }
600
601
            $methods = array(
602 6
                'generateInheritanceAnnotation',
603 6
                'generateDiscriminatorFieldAnnotation',
604 6
                'generateDiscriminatorMapAnnotation',
605 6
                'generateDefaultDiscriminatorValueAnnotation',
606
                'generateChangeTrackingPolicyAnnotation'
607 6
            );
608
609 6
            foreach ($methods as $method) {
610 6
                if ($code = $this->$method($metadata)) {
611 6
                    $lines[] = ' * ' . $code;
612 6
                }
613 6
            }
614 6
        }
615
616 6
        $lines[] = ' */';
617 6
        return implode("\n", $lines);
618
    }
619
620 6
    private function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
621
    {
622 6
        if ($metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
623
            return '@ODM\\InheritanceType("' . $this->getInheritanceTypeString($metadata->inheritanceType) . '")';
624
        }
625 6
    }
626
627 6
    private function generateDiscriminatorFieldAnnotation(ClassMetadataInfo $metadata)
628
    {
629 6
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
630
            return '@ODM\\DiscriminatorField(name="' . $metadata->discriminatorField . '")';
631
        }
632 6
    }
633
634 6
    private function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
635
    {
636 6
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
637
            $inheritanceClassMap = array();
638
639
            foreach ($metadata->discriminatorMap as $type => $class) {
640
                $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
641
            }
642
643
            return '@ODM\\DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
644
        }
645 6
    }
646
647 6
    private function generateDefaultDiscriminatorValueAnnotation(ClassMetadataInfo $metadata)
648
    {
649 6
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION && isset($metadata->defaultDiscriminatorValue)) {
650
            return '@ODM\\DefaultDiscriminatorValue("' . $metadata->defaultDiscriminatorValue . '")';
651
        }
652 6
    }
653
654 6
    private function generateChangeTrackingPolicyAnnotation(ClassMetadataInfo $metadata)
655
    {
656 6
        return '@ODM\\ChangeTrackingPolicy("' . $this->getChangeTrackingPolicyString($metadata->changeTrackingPolicy) . '")';
657
    }
658
659 7
    private function generateDocumentStubMethods(ClassMetadataInfo $metadata)
660
    {
661 7
        $methods = array();
662
663 7
        foreach ($metadata->fieldMappings as $fieldMapping) {
664 7
            if (isset($fieldMapping['id'])) {
665 7
                if ($metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) {
666
                    if ($code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
667
                        $methods[] = $code;
668
                    }
669
                }
670 7
                if ($code = $code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
671 6
                    $methods[] = $code;
672 6
                }
673 7
            } elseif ( ! isset($fieldMapping['association'])) {
674 7
                if ($code = $code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
675 7
                    $methods[] = $code;
676 7
                }
677 7
                if ($code = $code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
678 7
                    $methods[] = $code;
679 7
                }
680 7
            } elseif ($fieldMapping['type'] === ClassMetadataInfo::ONE) {
681 6
                $nullable = $this->isAssociationNullable($fieldMapping) ? 'null' : null;
682 6 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...
683 6
                    $methods[] = $code;
684 6
                }
685 6 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...
686 6
                    $methods[] = $code;
687 6
                }
688 6
            } elseif ($fieldMapping['type'] === ClassMetadataInfo::MANY) {
689 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...
690 6
                    $methods[] = $code;
691 6
                }
692 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...
693 6
                    $methods[] = $code;
694 6
                }
695 6
                if ($code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], '\Doctrine\Common\Collections\Collection')) {
696 6
                    $methods[] = $code;
697 6
                }
698 6
            }
699 7
        }
700
701 7
        return implode("\n\n", $methods);
702
    }
703
704
    /**
705
     * @param array $fieldMapping
706
     *
707
     * @return bool
708
     */
709 6
    protected function isAssociationNullable($fieldMapping)
710
    {
711 6
        return isset($fieldMapping['nullable']) && $fieldMapping['nullable'];
712
    }
713
714 7
    private function generateDocumentLifecycleCallbackMethods(ClassMetadataInfo $metadata)
715
    {
716 7
        if (empty($metadata->lifecycleCallbacks)) {
717 1
            return '';
718
        }
719
720 6
        $methods = array();
721
722 6
        foreach ($metadata->lifecycleCallbacks as $event => $callbacks) {
723 6
            foreach ($callbacks as $callback) {
724 6
                if ($code = $this->generateLifecycleCallbackMethod($event, $callback, $metadata)) {
725 6
                    $methods[] = $code;
726 6
                }
727 6
            }
728 6
        }
729
730 6
        return implode("\n\n", $methods);
731
    }
732
733 7
    private function generateDocumentAssociationMappingProperties(ClassMetadataInfo $metadata)
734
    {
735 7
        $lines = array();
736
737 7
        foreach ($metadata->fieldMappings as $fieldMapping) {
738 7
            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
739 7
                $metadata->isInheritedField($fieldMapping['fieldName'])) {
740 2
                continue;
741
            }
742 6
            if ( ! isset($fieldMapping['association'])) {
743 6
                continue;
744
            }
745
746 6
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($fieldMapping, $metadata);
0 ignored issues
show
Unused Code introduced by
The call to DocumentGenerator::gener...ppingPropertyDocBlock() has too many arguments starting with $metadata.

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...
747 6
            $lines[] = $this->spaces . 'protected $' . $fieldMapping['fieldName']
748 6
                . ($fieldMapping['type'] === ClassMetadataInfo::MANY ? ' = array()' : null) . ";\n";
749 7
        }
750
751 7
        return implode("\n", $lines);
752
    }
753
754 7
    private function generateDocumentFieldMappingProperties(ClassMetadataInfo $metadata)
755
    {
756 7
        $lines = array();
757
758 7
        foreach ($metadata->fieldMappings as $fieldMapping) {
759 7
            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
760 7
                $metadata->isInheritedField($fieldMapping['fieldName'])) {
761 2
                continue;
762
            }
763 6
            if (isset($fieldMapping['association']) && $fieldMapping['association']) {
764 6
                continue;
765
            }
766
767 6
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
768 6
            $lines[] = $this->spaces . 'protected $' . $fieldMapping['fieldName']
769 6
                . (isset($fieldMapping['default']) ? ' = ' . var_export($fieldMapping['default'], true) : null) . ";\n";
770 7
        }
771
772 7
        return implode("\n", $lines);
773
    }
774
775 7
    private function generateDocumentStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
776
    {
777
        // Add/remove methods should use the singular form of the field name
778 7
        $formattedFieldName = in_array($type, array('add', 'remove'))
779 7
            ? Inflector::singularize($fieldName)
780 7
            : $fieldName;
781
782 7
        $methodName = $type . Inflector::classify($formattedFieldName);
783 7
        $variableName = Inflector::camelize($formattedFieldName);
784
785 7
        if ($this->hasMethod($methodName, $metadata)) {
786 2
            return;
787
        }
788
789 7
        $description = ucfirst($type) . ' ' . $variableName;
790
791 7
        $types = Type::getTypesMap();
792 7
        $methodTypeHint = $typeHint && ! isset($types[$typeHint]) ? '\\' . $typeHint . ' ' : null;
793 7
        $variableType = $typeHint ? $typeHint . ' ' : null;
794
795
        $replacements = array(
796 7
            '<description>'         => $description,
797 7
            '<methodTypeHint>'      => $methodTypeHint,
798 7
            '<variableType>'        => $variableType,
799 7
            '<variableName>'        => $variableName,
800 7
            '<methodName>'          => $methodName,
801 7
            '<fieldName>'           => $fieldName,
802 7
            '<variableDefault>'     => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
803 7
        );
804
805 7
        $templateVar = sprintf('%sMethodTemplate', $type);
806
807 7
        $method = str_replace(
808 7
            array_keys($replacements),
809 7
            array_values($replacements),
810
            self::$$templateVar
811 7
        );
812
813 7
        return $this->prefixCodeWithSpaces($method);
814
    }
815
816 6
    private function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
817
    {
818 6
        if ($this->hasMethod($methodName, $metadata)) {
819 1
            return;
820
        }
821
822
        $replacements = array(
823 6
            '<comment>'    => $this->generateAnnotations ? '/** @ODM\\' . ucfirst($name) . ' */' : '',
824 6
            '<methodName>' => $methodName,
825 6
        );
826
827 6
        $method = str_replace(
828 6
            array_keys($replacements),
829 6
            array_values($replacements),
830
            self::$lifecycleCallbackMethodTemplate
831 6
        );
832
833 6
        return $this->prefixCodeWithSpaces($method);
834
    }
835
836 6
    private function generateAssociationMappingPropertyDocBlock(array $fieldMapping)
837
    {
838 6
        $lines = array();
839 6
        $lines[] = $this->spaces . '/**';
840 6
        $lines[] = $this->spaces . ' * @var ' . (isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : 'object');
841
842 6
        if ($this->generateAnnotations) {
843 6
            $lines[] = $this->spaces . ' *';
844
845 6
            $type = null;
846 6
            switch ($fieldMapping['association']) {
847 6
                case ClassMetadataInfo::EMBED_ONE:
848
                    $type = 'EmbedOne';
849
                    break;
850 6
                case ClassMetadataInfo::EMBED_MANY:
851
                    $type = 'EmbedMany';
852
                    break;
853 6
                case ClassMetadataInfo::REFERENCE_ONE:
854 6
                    $type = 'ReferenceOne';
855 6
                    break;
856 6
                case ClassMetadataInfo::REFERENCE_MANY:
857 6
                    $type = 'ReferenceMany';
858 6
                    break;
859 6
            }
860 6
            $typeOptions = array();
861
862 6
            if (isset($fieldMapping['targetDocument'])) {
863 6
                $typeOptions[] = 'targetDocument="' . $fieldMapping['targetDocument'] . '"';
864 6
            }
865
866 6
            if (isset($fieldMapping['cascade']) && $fieldMapping['cascade']) {
867
                $cascades = array();
868
869
                if ($fieldMapping['isCascadePersist']) $cascades[] = '"persist"';
870
                if ($fieldMapping['isCascadeRemove']) $cascades[] = '"remove"';
871
                if ($fieldMapping['isCascadeDetach']) $cascades[] = '"detach"';
872
                if ($fieldMapping['isCascadeMerge']) $cascades[] = '"merge"';
873
                if ($fieldMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
874
875
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
876
            }
877
878 6
            $lines[] = $this->spaces . ' * @ODM\\' . $type . '(' . implode(', ', $typeOptions) . ')';
879 6
        }
880
881 6
        $lines[] = $this->spaces . ' */';
882
883 6
        return implode("\n", $lines);
884
    }
885
886 6
    private function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
887
    {
888 6
        $lines = array();
889 6
        $lines[] = $this->spaces . '/**';
890 6
        if (isset($fieldMapping['id']) && $fieldMapping['id']) {
891 6
            $fieldMapping['strategy'] = isset($fieldMapping['strategy']) ? $fieldMapping['strategy'] : ClassMetadataInfo::GENERATOR_TYPE_AUTO;
892 6
            if ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_AUTO) {
893 6
                $lines[] = $this->spaces . ' * @var MongoId $' . $fieldMapping['fieldName'];
894 6 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...
895
                $lines[] = $this->spaces . ' * @var integer $' . $fieldMapping['fieldName'];
896
            } elseif ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_UUID) {
897
                $lines[] = $this->spaces . ' * @var string $' . $fieldMapping['fieldName'];
898 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...
899
                $lines[] = $this->spaces . ' * @var $' . $fieldMapping['fieldName'];
900
            } else {
901
                $lines[] = $this->spaces . ' * @var $' . $fieldMapping['fieldName'];
902
            }
903 6
        } else {
904 6
            $lines[] = $this->spaces . ' * @var ' . $fieldMapping['type'] . ' $' . $fieldMapping['fieldName'];
905
        }
906
907 6
        if ($this->generateAnnotations) {
908 6
            $lines[] = $this->spaces . ' *';
909
910 6
            $field = array();
911 6
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
912 6
                if (isset($fieldMapping['strategy'])) {
913 6
                    $field[] = 'strategy="' . $this->getIdGeneratorTypeString($metadata->generatorType) . '"';
914 6
                }
915 6
                $lines[] = $this->spaces . ' * @ODM\\Id(' . implode(', ', $field) . ')';
916 6
            } else {
917 6
                if (isset($fieldMapping['name'])) {
918 6
                    $field[] = 'name="' . $fieldMapping['name'] . '"';
919 6
                }
920
921 6
                if (isset($fieldMapping['type'])) {
922 6
                    $field[] = 'type="' . $fieldMapping['type'] . '"';
923 6
                }
924
925 6
                if (isset($fieldMapping['nullable']) && $fieldMapping['nullable'] === true) {
926
                    $field[] = 'nullable=' . var_export($fieldMapping['nullable'], true);
927
                }
928 6
                if (isset($fieldMapping['options'])) {
929
                    $options = array();
930
                    foreach ($fieldMapping['options'] as $key => $value) {
931
                        $options[] = '"' . $key . '" = "' . $value . '"';
932
                    }
933
                    $field[] = "options={" . implode(', ', $options) . "}";
934
                }
935 6
                $lines[] = $this->spaces . ' * @ODM\\Field(' . implode(', ', $field) . ')';
936
            }
937
938 6
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
939
                $lines[] = $this->spaces . ' * @ODM\\Version';
940
            }
941 6
        }
942
943 6
        $lines[] = $this->spaces . ' */';
944
945 6
        return implode("\n", $lines);
946
    }
947
948 7
    private function prefixCodeWithSpaces($code, $num = 1)
949
    {
950 7
        $lines = explode("\n", $code);
951
952 7
        foreach ($lines as $key => $value) {
953 7
            $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
954 7
        }
955
956 7
        return implode("\n", $lines);
957
    }
958
959
    private function getInheritanceTypeString($type)
960
    {
961
        switch ($type) {
962
            case ClassMetadataInfo::INHERITANCE_TYPE_NONE:
963
                return 'NONE';
964
965
            case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION:
966
                return 'SINGLE_COLLECTION';
967
968
            case ClassMetadataInfo::INHERITANCE_TYPE_COLLECTION_PER_CLASS:
969
                return 'COLLECTION_PER_CLASS';
970
971
            default:
972
                throw new \InvalidArgumentException('Invalid provided InheritanceType: ' . $type);
973
        }
974
    }
975
976 6
    private function getChangeTrackingPolicyString($policy)
977
    {
978
        switch ($policy) {
979 6
            case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT:
980 6
                return 'DEFERRED_IMPLICIT';
981
982
            case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT:
983
                return 'DEFERRED_EXPLICIT';
984
985
            case ClassMetadataInfo::CHANGETRACKING_NOTIFY:
986
                return 'NOTIFY';
987
988
            default:
989
                throw new \InvalidArgumentException('Invalid provided ChangeTrackingPolicy: ' . $policy);
990
        }
991
    }
992
993 6
    private function getIdGeneratorTypeString($type)
994
    {
995
        switch ($type) {
996 6
            case ClassMetadataInfo::GENERATOR_TYPE_AUTO:
997
                return 'AUTO';
998
999 6
            case ClassMetadataInfo::GENERATOR_TYPE_INCREMENT:
1000
                return 'INCREMENT';
1001
1002 6
            case ClassMetadataInfo::GENERATOR_TYPE_UUID:
1003
                return 'UUID';
1004
1005 6
            case ClassMetadataInfo::GENERATOR_TYPE_ALNUM:
1006
                return 'ALNUM';
1007
1008 6
            case ClassMetadataInfo::GENERATOR_TYPE_CUSTOM:
1009 6
                return 'CUSTOM';
1010
1011
            case ClassMetadataInfo::GENERATOR_TYPE_NONE:
1012
                return 'NONE';
1013
1014
            default:
1015
                throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
1016
        }
1017
    }
1018
}
1019