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
20namespace Doctrine\ORM\Tools;
21
22use Doctrine\ORM\Mapping\ClassMetadataInfo;
23use Doctrine\Common\Util\Inflector;
24use Doctrine\DBAL\Types\Type;
25
26/**
27 * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
28 *
29 *     [php]
30 *     $classes = $em->getClassMetadataFactory()->getAllMetadata();
31 *
32 *     $generator = new \Doctrine\ORM\Tools\EntityGenerator();
33 *     $generator->setGenerateAnnotations(true);
34 *     $generator->setGenerateStubMethods(true);
35 *     $generator->setRegenerateEntityIfExists(false);
36 *     $generator->setUpdateEntityIfExists(true);
37 *     $generator->generate($classes, '/path/to/generate/entities');
38 *
39 *
40 * @link    www.doctrine-project.org
41 * @since   2.0
42 * @author  Benjamin Eberlei <kontakt@beberlei.de>
43 * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
44 * @author  Jonathan Wage <jonwage@gmail.com>
45 * @author  Roman Borschel <roman@code-factory.org>
46 */
47class EntityGenerator
48{
49    /**
50     * Specifies class fields should be protected.
51     */
52    const FIELD_VISIBLE_PROTECTED = 'protected';
53
54    /**
55     * Specifies class fields should be private.
56     */
57    const FIELD_VISIBLE_PRIVATE = 'private';
58
59    /**
60     * @var bool
61     */
62    protected $backupExisting = true;
63
64    /**
65     * The extension to use for written php files.
66     *
67     * @var string
68     */
69    protected $extension = '.php';
70
71    /**
72     * Whether or not the current ClassMetadataInfo instance is new or old.
73     *
74     * @var boolean
75     */
76    protected $isNew = true;
77
78    /**
79     * @var array
80     */
81    protected $staticReflection = array();
82
83    /**
84     * Number of spaces to use for indention in generated code.
85     */
86    protected $numSpaces = 4;
87
88    /**
89     * The actual spaces to use for indention.
90     *
91     * @var string
92     */
93    protected $spaces = '    ';
94
95    /**
96     * The class all generated entities should extend.
97     *
98     * @var string
99     */
100    protected $classToExtend;
101
102    /**
103     * Whether or not to generation annotations.
104     *
105     * @var boolean
106     */
107    protected $generateAnnotations = false;
108
109    /**
110     * @var string
111     */
112    protected $annotationsPrefix = '';
113
114    /**
115     * Whether or not to generate sub methods.
116     *
117     * @var boolean
118     */
119    protected $generateEntityStubMethods = false;
120
121    /**
122     * Whether or not to update the entity class if it exists already.
123     *
124     * @var boolean
125     */
126    protected $updateEntityIfExists = false;
127
128    /**
129     * Whether or not to re-generate entity class if it exists already.
130     *
131     * @var boolean
132     */
133    protected $regenerateEntityIfExists = false;
134
135    /**
136     * Visibility of the field
137     *
138     * @var string
139     */
140    protected $fieldVisibility = 'private';
141
142    /**
143     * Whether or not to make generated embeddables immutable.
144     *
145     * @var boolean.
146     */
147    protected $embeddablesImmutable = false;
148
149    /**
150     * Hash-map for handle types.
151     *
152     * @var array
153     */
154    protected $typeAlias = array(
155        Type::DATETIMETZ    => '\DateTime',
156        Type::DATETIME      => '\DateTime',
157        Type::DATE          => '\DateTime',
158        Type::TIME          => '\DateTime',
159        Type::OBJECT        => '\stdClass',
160        Type::BIGINT        => 'integer',
161        Type::SMALLINT      => 'integer',
162        Type::TEXT          => 'string',
163        Type::BLOB          => 'string',
164        Type::DECIMAL       => 'string',
165        Type::JSON_ARRAY    => 'array',
166        Type::SIMPLE_ARRAY  => 'array',
167    );
168
169    /**
170     * Hash-map to handle generator types string.
171     *
172     * @var array
173     */
174    protected static $generatorStrategyMap = array(
175        ClassMetadataInfo::GENERATOR_TYPE_AUTO      => 'AUTO',
176        ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE  => 'SEQUENCE',
177        ClassMetadataInfo::GENERATOR_TYPE_TABLE     => 'TABLE',
178        ClassMetadataInfo::GENERATOR_TYPE_IDENTITY  => 'IDENTITY',
179        ClassMetadataInfo::GENERATOR_TYPE_NONE      => 'NONE',
180        ClassMetadataInfo::GENERATOR_TYPE_UUID      => 'UUID',
181        ClassMetadataInfo::GENERATOR_TYPE_CUSTOM    => 'CUSTOM'
182    );
183
184    /**
185     * Hash-map to handle the change tracking policy string.
186     *
187     * @var array
188     */
189    protected static $changeTrackingPolicyMap = array(
190        ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT  => 'DEFERRED_IMPLICIT',
191        ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT  => 'DEFERRED_EXPLICIT',
192        ClassMetadataInfo::CHANGETRACKING_NOTIFY             => 'NOTIFY',
193    );
194
195    /**
196     * Hash-map to handle the inheritance type string.
197     *
198     * @var array
199     */
200    protected static $inheritanceTypeMap = array(
201        ClassMetadataInfo::INHERITANCE_TYPE_NONE            => 'NONE',
202        ClassMetadataInfo::INHERITANCE_TYPE_JOINED          => 'JOINED',
203        ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE    => 'SINGLE_TABLE',
204        ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS',
205    );
206
207    /**
208     * @var string
209     */
210    protected static $classTemplate =
211'<?php
212
213<namespace>
214<useStatement>
215<entityAnnotation>
216<entityClassName>
217{
218<entityBody>
219}
220';
221
222    /**
223     * @var string
224     */
225    protected static $getMethodTemplate =
226'/**
227 * <description>
228 *
229 * @return <variableType>
230 */
231public function <methodName>()
232{
233<spaces>return $this-><fieldName>;
234}';
235
236    /**
237     * @var string
238     */
239    protected static $setMethodTemplate =
240'/**
241 * <description>
242 *
243 * @param <variableType> $<variableName>
244 *
245 * @return <entity>
246 */
247public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
248{
249<spaces>$this-><fieldName> = $<variableName>;
250
251<spaces>return $this;
252}';
253
254    /**
255     * @var string
256     */
257    protected static $addMethodTemplate =
258'/**
259 * <description>
260 *
261 * @param <variableType> $<variableName>
262 *
263 * @return <entity>
264 */
265public function <methodName>(<methodTypeHint>$<variableName>)
266{
267<spaces>$this-><fieldName>[] = $<variableName>;
268
269<spaces>return $this;
270}';
271
272    /**
273     * @var string
274     */
275    protected static $removeMethodTemplate =
276'/**
277 * <description>
278 *
279 * @param <variableType> $<variableName>
280 */
281public function <methodName>(<methodTypeHint>$<variableName>)
282{
283<spaces>$this-><fieldName>->removeElement($<variableName>);
284}';
285
286    /**
287     * @var string
288     */
289    protected static $lifecycleCallbackMethodTemplate =
290'/**
291 * @<name>
292 */
293public function <methodName>()
294{
295<spaces>// Add your code here
296}';
297
298    /**
299     * @var string
300     */
301    protected static $constructorMethodTemplate =
302'/**
303 * Constructor
304 */
305public function __construct()
306{
307<spaces><collections>
308}
309';
310
311    /**
312     * @var string
313     */
314    protected static $embeddableConstructorMethodTemplate =
315'/**
316 * Constructor
317 *
318 * <paramTags>
319 */
320public function __construct(<params>)
321{
322<spaces><fields>
323}
324';
325
326    /**
327     * Constructor.
328     */
329    public function __construct()
330    {
331        if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
332            $this->annotationsPrefix = 'ORM\\';
333        }
334    }
335
336    /**
337     * Generates and writes entity classes for the given array of ClassMetadataInfo instances.
338     *
339     * @param array  $metadatas
340     * @param string $outputDirectory
341     *
342     * @return void
343     */
344    public function generate(array $metadatas, $outputDirectory)
345    {
346        foreach ($metadatas as $metadata) {
347            $this->writeEntityClass($metadata, $outputDirectory);
348        }
349    }
350
351    /**
352     * Generates and writes entity class to disk for the given ClassMetadataInfo instance.
353     *
354     * @param ClassMetadataInfo $metadata
355     * @param string            $outputDirectory
356     *
357     * @return void
358     *
359     * @throws \RuntimeException
360     */
361    public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
362    {
363        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
364        $dir = dirname($path);
365
366        if ( ! is_dir($dir)) {
367            mkdir($dir, 0775, true);
368        }
369
370        $this->isNew = !file_exists($path) || (file_exists($path) && $this->regenerateEntityIfExists);
371
372        if ( ! $this->isNew) {
373            $this->parseTokensInEntityFile(file_get_contents($path));
374        } else {
375            $this->staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array());
376        }
377
378        if ($this->backupExisting && file_exists($path)) {
379            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
380            if (!copy($path, $backupPath)) {
381                throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
382            }
383        }
384
385        // If entity doesn't exist or we're re-generating the entities entirely
386        if ($this->isNew) {
387            file_put_contents($path, $this->generateEntityClass($metadata));
388        // If entity exists and we're allowed to update the entity class
389        } elseif ( ! $this->isNew && $this->updateEntityIfExists) {
390            file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
391        }
392        chmod($path, 0664);
393    }
394
395    /**
396     * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance.
397     *
398     * @param ClassMetadataInfo $metadata
399     *
400     * @return string
401     */
402    public function generateEntityClass(ClassMetadataInfo $metadata)
403    {
404        $placeHolders = array(
405            '<namespace>',
406            '<useStatement>',
407            '<entityAnnotation>',
408            '<entityClassName>',
409            '<entityBody>'
410        );
411
412        $replacements = array(
413            $this->generateEntityNamespace($metadata),
414            $this->generateEntityUse(),
415            $this->generateEntityDocBlock($metadata),
416            $this->generateEntityClassName($metadata),
417            $this->generateEntityBody($metadata)
418        );
419
420        $code = str_replace($placeHolders, $replacements, static::$classTemplate) . "\n";
421
422        return str_replace('<spaces>', $this->spaces, $code);
423    }
424
425    /**
426     * Generates the updated code for the given ClassMetadataInfo and entity at path.
427     *
428     * @param ClassMetadataInfo $metadata
429     * @param string            $path
430     *
431     * @return string
432     */
433    public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
434    {
435        $currentCode = file_get_contents($path);
436
437        $body = $this->generateEntityBody($metadata);
438        $body = str_replace('<spaces>', $this->spaces, $body);
439        $last = strrpos($currentCode, '}');
440
441        return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : '') . "}\n";
442    }
443
444    /**
445     * Sets the number of spaces the exported class should have.
446     *
447     * @param integer $numSpaces
448     *
449     * @return void
450     */
451    public function setNumSpaces($numSpaces)
452    {
453        $this->spaces = str_repeat(' ', $numSpaces);
454        $this->numSpaces = $numSpaces;
455    }
456
457    /**
458     * Sets the extension to use when writing php files to disk.
459     *
460     * @param string $extension
461     *
462     * @return void
463     */
464    public function setExtension($extension)
465    {
466        $this->extension = $extension;
467    }
468
469    /**
470     * Sets the name of the class the generated classes should extend from.
471     *
472     * @param string $classToExtend
473     *
474     * @return void
475     */
476    public function setClassToExtend($classToExtend)
477    {
478        $this->classToExtend = $classToExtend;
479    }
480
481    /**
482     * Sets whether or not to generate annotations for the entity.
483     *
484     * @param bool $bool
485     *
486     * @return void
487     */
488    public function setGenerateAnnotations($bool)
489    {
490        $this->generateAnnotations = $bool;
491    }
492
493    /**
494     * Sets the class fields visibility for the entity (can either be private or protected).
495     *
496     * @param bool $visibility
497     *
498     * @return void
499     *
500     * @throws \InvalidArgumentException
501     */
502    public function setFieldVisibility($visibility)
503    {
504        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
505            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
506        }
507
508        $this->fieldVisibility = $visibility;
509    }
510
511    /**
512     * Sets whether or not to generate immutable embeddables.
513     *
514     * @param boolean $embeddablesImmutable
515     */
516    public function setEmbeddablesImmutable($embeddablesImmutable)
517    {
518        $this->embeddablesImmutable = (boolean) $embeddablesImmutable;
519    }
520
521    /**
522     * Sets an annotation prefix.
523     *
524     * @param string $prefix
525     *
526     * @return void
527     */
528    public function setAnnotationPrefix($prefix)
529    {
530        $this->annotationsPrefix = $prefix;
531    }
532
533    /**
534     * Sets whether or not to try and update the entity if it already exists.
535     *
536     * @param bool $bool
537     *
538     * @return void
539     */
540    public function setUpdateEntityIfExists($bool)
541    {
542        $this->updateEntityIfExists = $bool;
543    }
544
545    /**
546     * Sets whether or not to regenerate the entity if it exists.
547     *
548     * @param bool $bool
549     *
550     * @return void
551     */
552    public function setRegenerateEntityIfExists($bool)
553    {
554        $this->regenerateEntityIfExists = $bool;
555    }
556
557    /**
558     * Sets whether or not to generate stub methods for the entity.
559     *
560     * @param bool $bool
561     *
562     * @return void
563     */
564    public function setGenerateStubMethods($bool)
565    {
566        $this->generateEntityStubMethods = $bool;
567    }
568
569    /**
570     * Should an existing entity be backed up if it already exists?
571     *
572     * @param bool $bool
573     *
574     * @return void
575     */
576    public function setBackupExisting($bool)
577    {
578        $this->backupExisting = $bool;
579    }
580
581    /**
582     * @param string $type
583     *
584     * @return string
585     */
586    protected function getType($type)
587    {
588        if (isset($this->typeAlias[$type])) {
589            return $this->typeAlias[$type];
590        }
591
592        return $type;
593    }
594
595    /**
596     * @param ClassMetadataInfo $metadata
597     *
598     * @return string
599     */
600    protected function generateEntityNamespace(ClassMetadataInfo $metadata)
601    {
602        if ($this->hasNamespace($metadata)) {
603            return 'namespace ' . $this->getNamespace($metadata) .';';
604        }
605    }
606
607    protected function generateEntityUse()
608    {
609        if ($this->generateAnnotations) {
610            return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
611        } else {
612            return "";
613        }
614    }
615
616    /**
617     * @param ClassMetadataInfo $metadata
618     *
619     * @return string
620     */
621    protected function generateEntityClassName(ClassMetadataInfo $metadata)
622    {
623        return 'class ' . $this->getClassName($metadata) .
624            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
625    }
626
627    /**
628     * @param ClassMetadataInfo $metadata
629     *
630     * @return string
631     */
632    protected function generateEntityBody(ClassMetadataInfo $metadata)
633    {
634        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
635        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
636        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
637        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
638        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
639
640        $code = array();
641
642        if ($fieldMappingProperties) {
643            $code[] = $fieldMappingProperties;
644        }
645
646        if ($embeddedProperties) {
647            $code[] = $embeddedProperties;
648        }
649
650        if ($associationMappingProperties) {
651            $code[] = $associationMappingProperties;
652        }
653
654        $code[] = $this->generateEntityConstructor($metadata);
655
656        if ($stubMethods) {
657            $code[] = $stubMethods;
658        }
659
660        if ($lifecycleCallbackMethods) {
661            $code[] = $lifecycleCallbackMethods;
662        }
663
664        return implode("\n", $code);
665    }
666
667    /**
668     * @param ClassMetadataInfo $metadata
669     *
670     * @return string
671     */
672    protected function generateEntityConstructor(ClassMetadataInfo $metadata)
673    {
674        if ($this->hasMethod('__construct', $metadata)) {
675            return '';
676        }
677
678        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
679            return $this->generateEmbeddableConstructor($metadata);
680        }
681
682        $collections = array();
683
684        foreach ($metadata->associationMappings as $mapping) {
685            if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
686                $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
687            }
688        }
689
690        if ($collections) {
691            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
692        }
693
694        return '';
695    }
696
697    /**
698     * @param ClassMetadataInfo $metadata
699     *
700     * @return string
701     */
702    private function generateEmbeddableConstructor(ClassMetadataInfo $metadata)
703    {
704        $paramTypes = array();
705        $paramVariables = array();
706        $params = array();
707        $fields = array();
708
709        // Resort fields to put optional fields at the end of the method signature.
710        $requiredFields = array();
711        $optionalFields = array();
712
713        foreach ($metadata->fieldMappings as $fieldMapping) {
714            if (empty($fieldMapping['nullable'])) {
715                $requiredFields[] = $fieldMapping;
716
717                continue;
718            }
719
720            $optionalFields[] = $fieldMapping;
721        }
722
723        $fieldMappings = array_merge($requiredFields, $optionalFields);
724
725        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
726            $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
727            $paramVariable = '$' . $fieldName;
728
729            $paramTypes[] = $paramType;
730            $paramVariables[] = $paramVariable;
731            $params[] = $paramType . ' ' . $paramVariable;
732            $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
733        }
734
735        foreach ($fieldMappings as $fieldMapping) {
736            if (isset($fieldMapping['declaredField']) &&
737                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
738            ) {
739                continue;
740            }
741
742            $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : '');
743            $param = '$' . $fieldMapping['fieldName'];
744            $paramVariables[] = $param;
745
746            if ($fieldMapping['type'] === 'datetime') {
747                $param = $this->getType($fieldMapping['type']) . ' ' . $param;
748            }
749
750            if (!empty($fieldMapping['nullable'])) {
751                $param .= ' = null';
752            }
753
754            $params[] = $param;
755
756            $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
757        }
758
759        $maxParamTypeLength = max(array_map('strlen', $paramTypes));
760        $paramTags = array_map(
761            function ($type, $variable) use ($maxParamTypeLength) {
762                return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
763            },
764            $paramTypes,
765            $paramVariables
766        );
767
768        // Generate multi line constructor if the signature exceeds 120 characters.
769        if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
770            $delimiter = "\n" . $this->spaces;
771            $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
772        } else {
773            $params = implode(', ', $params);
774        }
775
776        $replacements = array(
777            '<paramTags>' => implode("\n * ", $paramTags),
778            '<params>'    => $params,
779            '<fields>'    => implode("\n" . $this->spaces, $fields),
780        );
781
782        $constructor = str_replace(
783            array_keys($replacements),
784            array_values($replacements),
785            static::$embeddableConstructorMethodTemplate
786        );
787
788        return $this->prefixCodeWithSpaces($constructor);
789    }
790
791    /**
792     * @todo this won't work if there is a namespace in brackets and a class outside of it.
793     *
794     * @param string $src
795     *
796     * @return void
797     */
798    protected function parseTokensInEntityFile($src)
799    {
800        $tokens = token_get_all($src);
801        $lastSeenNamespace = "";
802        $lastSeenClass = false;
803
804        $inNamespace = false;
805        $inClass = false;
806
807        for ($i = 0; $i < count($tokens); $i++) {
808            $token = $tokens[$i];
809            if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
810                continue;
811            }
812
813            if ($inNamespace) {
814                if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
815                    $lastSeenNamespace .= $token[1];
816                } elseif (is_string($token) && in_array($token, array(';', '{'))) {
817                    $inNamespace = false;
818                }
819            }
820
821            if ($inClass) {
822                $inClass = false;
823                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
824                $this->staticReflection[$lastSeenClass]['properties'] = array();
825                $this->staticReflection[$lastSeenClass]['methods'] = array();
826            }
827
828            if ($token[0] == T_NAMESPACE) {
829                $lastSeenNamespace = "";
830                $inNamespace = true;
831            } elseif ($token[0] == T_CLASS && $tokens[$i-1][0] != T_DOUBLE_COLON) {
832                $inClass = true;
833            } elseif ($token[0] == T_FUNCTION) {
834                if ($tokens[$i+2][0] == T_STRING) {
835                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
836                } elseif ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
837                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
838                }
839            } elseif (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
840                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
841            }
842        }
843    }
844
845    /**
846     * @param string            $property
847     * @param ClassMetadataInfo $metadata
848     *
849     * @return bool
850     */
851    protected function hasProperty($property, ClassMetadataInfo $metadata)
852    {
853        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
854            // don't generate property if its already on the base class.
855            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
856            if ($reflClass->hasProperty($property)) {
857                return true;
858            }
859        }
860
861        // check traits for existing property
862        foreach ($this->getTraits($metadata) as $trait) {
863            if ($trait->hasProperty($property)) {
864                return true;
865            }
866        }
867
868        return (
869            isset($this->staticReflection[$metadata->name]) &&
870            in_array($property, $this->staticReflection[$metadata->name]['properties'])
871        );
872    }
873
874    /**
875     * @param string            $method
876     * @param ClassMetadataInfo $metadata
877     *
878     * @return bool
879     */
880    protected function hasMethod($method, ClassMetadataInfo $metadata)
881    {
882        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
883            // don't generate method if its already on the base class.
884            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
885
886            if ($reflClass->hasMethod($method)) {
887                return true;
888            }
889        }
890
891        // check traits for existing method
892        foreach ($this->getTraits($metadata) as $trait) {
893            if ($trait->hasMethod($method)) {
894                return true;
895            }
896        }
897
898        return (
899            isset($this->staticReflection[$metadata->name]) &&
900            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'])
901        );
902    }
903
904    /**
905     * @param ClassMetadataInfo $metadata
906     *
907     * @return array
908     */
909    protected function getTraits(ClassMetadataInfo $metadata)
910    {
911        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
912            return [];
913        }
914
915        $reflClass = $metadata->reflClass === null
916            ? new \ReflectionClass($metadata->name)
917            : $metadata->reflClass;
918
919        $traits = array();
920
921        while ($reflClass !== false) {
922            $traits = array_merge($traits, $reflClass->getTraits());
923
924            $reflClass = $reflClass->getParentClass();
925        }
926
927        return $traits;
928    }
929
930    /**
931     * @param ClassMetadataInfo $metadata
932     *
933     * @return bool
934     */
935    protected function hasNamespace(ClassMetadataInfo $metadata)
936    {
937        return strpos($metadata->name, '\\') ? true : false;
938    }
939
940    /**
941     * @return bool
942     */
943    protected function extendsClass()
944    {
945        return $this->classToExtend ? true : false;
946    }
947
948    /**
949     * @return string
950     */
951    protected function getClassToExtend()
952    {
953        return $this->classToExtend;
954    }
955
956    /**
957     * @return string
958     */
959    protected function getClassToExtendName()
960    {
961        $refl = new \ReflectionClass($this->getClassToExtend());
962
963        return '\\' . $refl->getName();
964    }
965
966    /**
967     * @param ClassMetadataInfo $metadata
968     *
969     * @return string
970     */
971    protected function getClassName(ClassMetadataInfo $metadata)
972    {
973        return ($pos = strrpos($metadata->name, '\\'))
974            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
975    }
976
977    /**
978     * @param ClassMetadataInfo $metadata
979     *
980     * @return string
981     */
982    protected function getNamespace(ClassMetadataInfo $metadata)
983    {
984        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
985    }
986
987    /**
988     * @param ClassMetadataInfo $metadata
989     *
990     * @return string
991     */
992    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
993    {
994        $lines = array();
995        $lines[] = '/**';
996        $lines[] = ' * ' . $this->getClassName($metadata);
997
998        if ($this->generateAnnotations) {
999            $lines[] = ' *';
1000
1001            $methods = array(
1002                'generateTableAnnotation',
1003                'generateInheritanceAnnotation',
1004                'generateDiscriminatorColumnAnnotation',
1005                'generateDiscriminatorMapAnnotation',
1006                'generateEntityAnnotation',
1007            );
1008
1009            foreach ($methods as $method) {
1010                if ($code = $this->$method($metadata)) {
1011                    $lines[] = ' * ' . $code;
1012                }
1013            }
1014
1015            if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
1016                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1017            }
1018        }
1019
1020        $lines[] = ' */';
1021
1022        return implode("\n", $lines);
1023    }
1024
1025    /**
1026     * @param ClassMetadataInfo $metadata
1027     *
1028     * @return string
1029     */
1030    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1031    {
1032        $prefix = '@' . $this->annotationsPrefix;
1033
1034        if ($metadata->isEmbeddedClass) {
1035            return $prefix . 'Embeddable';
1036        }
1037
1038        $customRepository = $metadata->customRepositoryClassName
1039            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1040            : '';
1041
1042        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1043    }
1044
1045    /**
1046     * @param ClassMetadataInfo $metadata
1047     *
1048     * @return string
1049     */
1050    protected function generateTableAnnotation($metadata)
1051    {
1052        if ($metadata->isEmbeddedClass) {
1053            return '';
1054        }
1055
1056        $table = array();
1057
1058        if (isset($metadata->table['schema'])) {
1059            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1060        }
1061
1062        if (isset($metadata->table['name'])) {
1063            $table[] = 'name="' . $metadata->table['name'] . '"';
1064        }
1065
1066        if (isset($metadata->table['options']) && $metadata->table['options']) {
1067            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1068        }
1069
1070        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1071            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1072            $table[] = 'uniqueConstraints={' . $constraints . '}';
1073        }
1074
1075        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1076            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1077            $table[] = 'indexes={' . $constraints . '}';
1078        }
1079
1080        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1081    }
1082
1083    /**
1084     * @param string $constraintName
1085     * @param array  $constraints
1086     *
1087     * @return string
1088     */
1089    protected function generateTableConstraints($constraintName, $constraints)
1090    {
1091        $annotations = array();
1092        foreach ($constraints as $name => $constraint) {
1093            $columns = array();
1094            foreach ($constraint['columns'] as $column) {
1095                $columns[] = '"' . $column . '"';
1096            }
1097            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1098        }
1099        return implode(', ', $annotations);
1100    }
1101
1102    /**
1103     * @param ClassMetadataInfo $metadata
1104     *
1105     * @return string
1106     */
1107    protected function generateInheritanceAnnotation($metadata)
1108    {
1109        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1110            return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1111        }
1112    }
1113
1114    /**
1115     * @param ClassMetadataInfo $metadata
1116     *
1117     * @return string
1118     */
1119    protected function generateDiscriminatorColumnAnnotation($metadata)
1120    {
1121        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1122            $discrColumn = $metadata->discriminatorColumn;
1123            $columnDefinition = 'name="' . $discrColumn['name']
1124                . '", type="' . $discrColumn['type']
1125                . '", length=' . $discrColumn['length'];
1126
1127            return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1128        }
1129    }
1130
1131    /**
1132     * @param ClassMetadataInfo $metadata
1133     *
1134     * @return string
1135     */
1136    protected function generateDiscriminatorMapAnnotation($metadata)
1137    {
1138        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1139            $inheritanceClassMap = array();
1140
1141            foreach ($metadata->discriminatorMap as $type => $class) {
1142                $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1143            }
1144
1145            return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1146        }
1147    }
1148
1149    /**
1150     * @param ClassMetadataInfo $metadata
1151     *
1152     * @return string
1153     */
1154    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1155    {
1156        $methods = array();
1157
1158        foreach ($metadata->fieldMappings as $fieldMapping) {
1159            if (isset($fieldMapping['declaredField']) &&
1160                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1161            ) {
1162                continue;
1163            }
1164
1165            if (( ! isset($fieldMapping['id']) ||
1166                    ! $fieldMapping['id'] ||
1167                    $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE
1168                ) && (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable)
1169            ) {
1170                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
1171                    $methods[] = $code;
1172                }
1173            }
1174
1175            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
1176                $methods[] = $code;
1177            }
1178        }
1179
1180        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1181            if (isset($embeddedClass['declaredField'])) {
1182                continue;
1183            }
1184
1185            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1186                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1187                    $methods[] = $code;
1188                }
1189            }
1190
1191            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1192                $methods[] = $code;
1193            }
1194        }
1195
1196        foreach ($metadata->associationMappings as $associationMapping) {
1197            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1198                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1199                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1200                    $methods[] = $code;
1201                }
1202                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1203                    $methods[] = $code;
1204                }
1205            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1206                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1207                    $methods[] = $code;
1208                }
1209                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1210                    $methods[] = $code;
1211                }
1212                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
1213                    $methods[] = $code;
1214                }
1215            }
1216        }
1217
1218        return implode("\n\n", $methods);
1219    }
1220
1221    /**
1222     * @param array $associationMapping
1223     *
1224     * @return bool
1225     */
1226    protected function isAssociationIsNullable($associationMapping)
1227    {
1228        if (isset($associationMapping['id']) && $associationMapping['id']) {
1229            return false;
1230        }
1231
1232        if (isset($associationMapping['joinColumns'])) {
1233            $joinColumns = $associationMapping['joinColumns'];
1234        } else {
1235            //@todo there is no way to retrieve targetEntity metadata
1236            $joinColumns = array();
1237        }
1238
1239        foreach ($joinColumns as $joinColumn) {
1240            if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1241                return false;
1242            }
1243        }
1244
1245        return true;
1246    }
1247
1248    /**
1249     * @param ClassMetadataInfo $metadata
1250     *
1251     * @return string
1252     */
1253    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1254    {
1255        if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
1256            $methods = array();
1257
1258            foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1259                foreach ($callbacks as $callback) {
1260                    if ($code = $this->generateLifecycleCallbackMethod($name, $callback, $metadata)) {
1261                        $methods[] = $code;
1262                    }
1263                }
1264            }
1265
1266            return implode("\n\n", $methods);
1267        }
1268
1269        return "";
1270    }
1271
1272    /**
1273     * @param ClassMetadataInfo $metadata
1274     *
1275     * @return string
1276     */
1277    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1278    {
1279        $lines = array();
1280
1281        foreach ($metadata->associationMappings as $associationMapping) {
1282            if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
1283                continue;
1284            }
1285
1286            $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
1287            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
1288                     . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
1289        }
1290
1291        return implode("\n", $lines);
1292    }
1293
1294    /**
1295     * @param ClassMetadataInfo $metadata
1296     *
1297     * @return string
1298     */
1299    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1300    {
1301        $lines = array();
1302
1303        foreach ($metadata->fieldMappings as $fieldMapping) {
1304            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1305                $metadata->isInheritedField($fieldMapping['fieldName']) ||
1306                (
1307                    isset($fieldMapping['declaredField']) &&
1308                    isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1309                )
1310            ) {
1311                continue;
1312            }
1313
1314            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1315            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
1316                     . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
1317        }
1318
1319        return implode("\n", $lines);
1320    }
1321
1322    /**
1323     * @param ClassMetadataInfo $metadata
1324     *
1325     * @return string
1326     */
1327    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1328    {
1329        $lines = array();
1330
1331        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1332            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1333                continue;
1334            }
1335
1336            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1337            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1338        }
1339
1340        return implode("\n", $lines);
1341    }
1342
1343    /**
1344     * @param ClassMetadataInfo $metadata
1345     * @param string            $type
1346     * @param string            $fieldName
1347     * @param string|null       $typeHint
1348     * @param string|null       $defaultValue
1349     *
1350     * @return string
1351     */
1352    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null,  $defaultValue = null)
1353    {
1354        $methodName = $type . Inflector::classify($fieldName);
1355        $variableName = Inflector::camelize($fieldName);
1356        if (in_array($type, array("add", "remove"))) {
1357            $methodName = Inflector::singularize($methodName);
1358            $variableName = Inflector::singularize($variableName);
1359        }
1360
1361        if ($this->hasMethod($methodName, $metadata)) {
1362            return '';
1363        }
1364        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1365
1366        $var = sprintf('%sMethodTemplate', $type);
1367        $template = static::$$var;
1368
1369        $methodTypeHint = null;
1370        $types          = Type::getTypesMap();
1371        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1372
1373        if ($typeHint && ! isset($types[$typeHint])) {
1374            $variableType   =  '\\' . ltrim($variableType, '\\');
1375            $methodTypeHint =  '\\' . $typeHint . ' ';
1376        }
1377
1378        $replacements = array(
1379          '<description>'       => ucfirst($type) . ' ' . $variableName,
1380          '<methodTypeHint>'    => $methodTypeHint,
1381          '<variableType>'      => $variableType,
1382          '<variableName>'      => $variableName,
1383          '<methodName>'        => $methodName,
1384          '<fieldName>'         => $fieldName,
1385          '<variableDefault>'   => ($defaultValue !== null ) ? (' = '.$defaultValue) : '',
1386          '<entity>'            => $this->getClassName($metadata)
1387        );
1388
1389        $method = str_replace(
1390            array_keys($replacements),
1391            array_values($replacements),
1392            $template
1393        );
1394
1395        return $this->prefixCodeWithSpaces($method);
1396    }
1397
1398    /**
1399     * @param string            $name
1400     * @param string            $methodName
1401     * @param ClassMetadataInfo $metadata
1402     *
1403     * @return string
1404     */
1405    protected function generateLifecycleCallbackMethod($name, $methodName, $metadata)
1406    {
1407        if ($this->hasMethod($methodName, $metadata)) {
1408            return '';
1409        }
1410        $this->staticReflection[$metadata->name]['methods'][] = $methodName;
1411
1412        $replacements = array(
1413            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1414            '<methodName>'  => $methodName,
1415        );
1416
1417        $method = str_replace(
1418            array_keys($replacements),
1419            array_values($replacements),
1420            static::$lifecycleCallbackMethodTemplate
1421        );
1422
1423        return $this->prefixCodeWithSpaces($method);
1424    }
1425
1426    /**
1427     * @param array $joinColumn
1428     *
1429     * @return string
1430     */
1431    protected function generateJoinColumnAnnotation(array $joinColumn)
1432    {
1433        $joinColumnAnnot = array();
1434
1435        if (isset($joinColumn['name'])) {
1436            $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
1437        }
1438
1439        if (isset($joinColumn['referencedColumnName'])) {
1440            $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
1441        }
1442
1443        if (isset($joinColumn['unique']) && $joinColumn['unique']) {
1444            $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
1445        }
1446
1447        if (isset($joinColumn['nullable'])) {
1448            $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
1449        }
1450
1451        if (isset($joinColumn['onDelete'])) {
1452            $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
1453        }
1454
1455        if (isset($joinColumn['columnDefinition'])) {
1456            $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
1457        }
1458
1459        return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
1460    }
1461
1462    /**
1463     * @param array             $associationMapping
1464     * @param ClassMetadataInfo $metadata
1465     *
1466     * @return string
1467     */
1468    protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
1469    {
1470        $lines = array();
1471        $lines[] = $this->spaces . '/**';
1472
1473        if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1474            $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
1475        } else {
1476            $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
1477        }
1478
1479        if ($this->generateAnnotations) {
1480            $lines[] = $this->spaces . ' *';
1481
1482            if (isset($associationMapping['id']) && $associationMapping['id']) {
1483                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1484
1485                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1486                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1487                }
1488            }
1489
1490            $type = null;
1491            switch ($associationMapping['type']) {
1492                case ClassMetadataInfo::ONE_TO_ONE:
1493                    $type = 'OneToOne';
1494                    break;
1495                case ClassMetadataInfo::MANY_TO_ONE:
1496                    $type = 'ManyToOne';
1497                    break;
1498                case ClassMetadataInfo::ONE_TO_MANY:
1499                    $type = 'OneToMany';
1500                    break;
1501                case ClassMetadataInfo::MANY_TO_MANY:
1502                    $type = 'ManyToMany';
1503                    break;
1504            }
1505            $typeOptions = array();
1506
1507            if (isset($associationMapping['targetEntity'])) {
1508                $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1509            }
1510
1511            if (isset($associationMapping['inversedBy'])) {
1512                $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1513            }
1514
1515            if (isset($associationMapping['mappedBy'])) {
1516                $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1517            }
1518
1519            if ($associationMapping['cascade']) {
1520                $cascades = array();
1521
1522                if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1523                if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1524                if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1525                if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1526                if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1527
1528                if (count($cascades) === 5) {
1529                    $cascades = array('"all"');
1530                }
1531
1532                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1533            }
1534
1535            if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1536                $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1537            }
1538
1539            if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
1540                $fetchMap = array(
1541                    ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
1542                    ClassMetadataInfo::FETCH_EAGER      => 'EAGER',
1543                );
1544
1545                $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
1546            }
1547
1548            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1549
1550            if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1551                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1552
1553                $joinColumnsLines = array();
1554
1555                foreach ($associationMapping['joinColumns'] as $joinColumn) {
1556                    if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1557                        $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1558                    }
1559                }
1560
1561                $lines[] = implode(",\n", $joinColumnsLines);
1562                $lines[] = $this->spaces . ' * })';
1563            }
1564
1565            if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1566                $joinTable = array();
1567                $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1568
1569                if (isset($associationMapping['joinTable']['schema'])) {
1570                    $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1571                }
1572
1573                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1574                $lines[] = $this->spaces . ' *   joinColumns={';
1575
1576                $joinColumnsLines = array();
1577
1578                foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
1579                    $joinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1580                }
1581
1582                $lines[] = implode(",". PHP_EOL, $joinColumnsLines);
1583                $lines[] = $this->spaces . ' *   },';
1584                $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1585
1586                $inverseJoinColumnsLines = array();
1587
1588                foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1589                    $inverseJoinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1590                }
1591
1592                $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines);
1593                $lines[] = $this->spaces . ' *   }';
1594                $lines[] = $this->spaces . ' * )';
1595            }
1596
1597            if (isset($associationMapping['orderBy'])) {
1598                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1599
1600                foreach ($associationMapping['orderBy'] as $name => $direction) {
1601                    $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1602                }
1603
1604                $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1605                $lines[] = $this->spaces . ' * })';
1606            }
1607        }
1608
1609        $lines[] = $this->spaces . ' */';
1610
1611        return implode("\n", $lines);
1612    }
1613
1614    /**
1615     * @param array             $fieldMapping
1616     * @param ClassMetadataInfo $metadata
1617     *
1618     * @return string
1619     */
1620    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1621    {
1622        $lines = array();
1623        $lines[] = $this->spaces . '/**';
1624        $lines[] = $this->spaces . ' * @var ' . $this->getType($fieldMapping['type']);
1625
1626        if ($this->generateAnnotations) {
1627            $lines[] = $this->spaces . ' *';
1628
1629            $column = array();
1630            if (isset($fieldMapping['columnName'])) {
1631                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1632            }
1633
1634            if (isset($fieldMapping['type'])) {
1635                $column[] = 'type="' . $fieldMapping['type'] . '"';
1636            }
1637
1638            if (isset($fieldMapping['length'])) {
1639                $column[] = 'length=' . $fieldMapping['length'];
1640            }
1641
1642            if (isset($fieldMapping['precision'])) {
1643                $column[] = 'precision=' .  $fieldMapping['precision'];
1644            }
1645
1646            if (isset($fieldMapping['scale'])) {
1647                $column[] = 'scale=' . $fieldMapping['scale'];
1648            }
1649
1650            if (isset($fieldMapping['nullable'])) {
1651                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1652            }
1653
1654            if (isset($fieldMapping['unsigned']) && $fieldMapping['unsigned']) {
1655                $column[] = 'options={"unsigned"=true}';
1656            }
1657
1658            if (isset($fieldMapping['columnDefinition'])) {
1659                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1660            }
1661
1662            if (isset($fieldMapping['unique'])) {
1663                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1664            }
1665
1666            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1667
1668            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1669                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1670
1671                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1672                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1673                }
1674
1675                if ($metadata->sequenceGeneratorDefinition) {
1676                    $sequenceGenerator = array();
1677
1678                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1679                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1680                    }
1681
1682                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1683                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1684                    }
1685
1686                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1687                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1688                    }
1689
1690                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1691                }
1692            }
1693
1694            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1695                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1696            }
1697        }
1698
1699        $lines[] = $this->spaces . ' */';
1700
1701        return implode("\n", $lines);
1702    }
1703
1704    /**
1705     * @param array $embeddedClass
1706     *
1707     * @return string
1708     */
1709    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1710    {
1711        $lines = array();
1712        $lines[] = $this->spaces . '/**';
1713        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1714
1715        if ($this->generateAnnotations) {
1716            $lines[] = $this->spaces . ' *';
1717
1718            $embedded = array('class="' . $embeddedClass['class'] . '"');
1719
1720            if (isset($fieldMapping['columnPrefix'])) {
1721                $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1722            }
1723
1724            $lines[] = $this->spaces . ' * @' .
1725                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1726        }
1727
1728        $lines[] = $this->spaces . ' */';
1729
1730        return implode("\n", $lines);
1731    }
1732
1733    /**
1734     * @param string $code
1735     * @param int    $num
1736     *
1737     * @return string
1738     */
1739    protected function prefixCodeWithSpaces($code, $num = 1)
1740    {
1741        $lines = explode("\n", $code);
1742
1743        foreach ($lines as $key => $value) {
1744            if ( ! empty($value)) {
1745                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1746            }
1747        }
1748
1749        return implode("\n", $lines);
1750    }
1751
1752    /**
1753     * @param integer $type The inheritance type used by the class and its subclasses.
1754     *
1755     * @return string The literal string for the inheritance type.
1756     *
1757     * @throws \InvalidArgumentException When the inheritance type does not exist.
1758     */
1759    protected function getInheritanceTypeString($type)
1760    {
1761        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1762            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1763        }
1764
1765        return static::$inheritanceTypeMap[$type];
1766    }
1767
1768    /**
1769     * @param integer $type The policy used for change-tracking for the mapped class.
1770     *
1771     * @return string The literal string for the change-tracking type.
1772     *
1773     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1774     */
1775    protected function getChangeTrackingPolicyString($type)
1776    {
1777        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1778            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1779        }
1780
1781        return static::$changeTrackingPolicyMap[$type];
1782    }
1783
1784    /**
1785     * @param integer $type The generator to use for the mapped class.
1786     *
1787     * @return string The literal string for the generator type.
1788     *
1789     * @throws \InvalidArgumentException    When the generator type does not exist.
1790     */
1791    protected function getIdGeneratorTypeString($type)
1792    {
1793        if ( ! isset(static::$generatorStrategyMap[$type])) {
1794            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1795        }
1796
1797        return static::$generatorStrategyMap[$type];
1798    }
1799
1800    /**
1801     * Exports (nested) option elements.
1802     *
1803     * @param array $options
1804     */
1805    private function exportTableOptions(array $options)
1806    {
1807        $optionsStr = array();
1808
1809        foreach($options as $name => $option) {
1810            if (is_array($option)) {
1811                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1812            } else {
1813                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1814            }
1815        }
1816
1817        return implode(',', $optionsStr);
1818    }
1819}
1820