Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Tools / EntityGenerator.php
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\ORM\Tools;
21
22 use Doctrine\ORM\Mapping\ClassMetadataInfo,
23     Doctrine\Common\Util\Inflector,
24     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  */
47 class 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     private $backupExisting = true;
63
64     /**
65      * The extension to use for written php files
66      *
67      * @var string
68      */
69     private $extension = '.php';
70
71     /**
72      * Whether or not the current ClassMetadataInfo instance is new or old
73      *
74      * @var boolean
75      */
76     private $isNew = true;
77
78     /**
79      * @var array
80      */
81     private $staticReflection = array();
82
83     /**
84      * Number of spaces to use for indention in generated code
85      */
86     private $numSpaces = 4;
87
88     /**
89      * The actual spaces to use for indention
90      *
91      * @var string
92      */
93     private $spaces = '    ';
94
95     /**
96      * The class all generated entities should extend
97      *
98      * @var string
99      */
100     private $classToExtend;
101
102     /**
103      * Whether or not to generation annotations
104      *
105      * @var boolean
106      */
107     private $generateAnnotations = false;
108
109     /**
110      * @var string
111      */
112     private $annotationsPrefix = '';
113
114     /**
115      * Whether or not to generated sub methods
116      *
117      * @var boolean
118      */
119     private $generateEntityStubMethods = false;
120
121     /**
122      * Whether or not to update the entity class if it exists already
123      *
124      * @var boolean
125      */
126     private $updateEntityIfExists = false;
127
128     /**
129      * Whether or not to re-generate entity class if it exists already
130      *
131      * @var boolean
132      */
133     private $regenerateEntityIfExists = false;
134
135     /**
136      * @var boolean
137      */
138     private $fieldVisibility = 'private';
139
140     /**
141      * Hash-map for handle types
142      *
143      * @var array
144      */
145     private $typeAlias = array(
146         Type::DATETIMETZ    => '\DateTime',
147         Type::DATETIME      => '\DateTime',
148         Type::DATE          => '\DateTime',
149         Type::TIME          => '\DateTime',
150         Type::OBJECT        => '\stdClass',
151         Type::BIGINT        => 'integer',
152         Type::SMALLINT      => 'integer',
153         Type::TEXT          => 'string',
154         Type::BLOB          => 'string',
155         Type::DECIMAL       => 'float',
156         Type::JSON_ARRAY    => 'array',
157         Type::SIMPLE_ARRAY  => 'array',
158     );
159
160     /**
161      * @var string
162      */
163     private static $classTemplate =
164 '<?php
165
166 <namespace>
167
168 use Doctrine\ORM\Mapping as ORM;
169
170 <entityAnnotation>
171 <entityClassName>
172 {
173 <entityBody>
174 }
175 ';
176
177     /**
178      * @var string
179      */
180     private static $getMethodTemplate =
181 '/**
182  * <description>
183  *
184  * @return <variableType>
185  */
186 public function <methodName>()
187 {
188 <spaces>return $this-><fieldName>;
189 }';
190
191     /**
192      * @var string
193      */
194     private static $setMethodTemplate =
195 '/**
196  * <description>
197  *
198  * @param <variableType>$<variableName>
199  * @return <entity>
200  */
201 public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
202 {
203 <spaces>$this-><fieldName> = $<variableName>;
204
205 <spaces>return $this;
206 }';
207
208     /**
209      * @var string
210      */
211     private static $addMethodTemplate =
212 '/**
213  * <description>
214  *
215  * @param <variableType>$<variableName>
216  * @return <entity>
217  */
218 public function <methodName>(<methodTypeHint>$<variableName>)
219 {
220 <spaces>$this-><fieldName>[] = $<variableName>;
221
222 <spaces>return $this;
223 }';
224
225     /**
226      * @var string
227      */
228     private static $removeMethodTemplate =
229 '/**
230  * <description>
231  *
232  * @param <variableType>$<variableName>
233  */
234 public function <methodName>(<methodTypeHint>$<variableName>)
235 {
236 <spaces>$this-><fieldName>->removeElement($<variableName>);
237 }';
238
239     /**
240      * @var string
241      */
242     private static $lifecycleCallbackMethodTemplate =
243 '/**
244  * @<name>
245  */
246 public function <methodName>()
247 {
248 <spaces>// Add your code here
249 }';
250
251     /**
252      * @var string
253      */
254     private static $constructorMethodTemplate =
255 '/**
256  * Constructor
257  */
258 public function __construct()
259 {
260 <spaces><collections>
261 }
262 ';
263
264     public function __construct()
265     {
266         if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
267             $this->annotationsPrefix = 'ORM\\';
268         }
269     }
270
271     /**
272      * Generate and write entity classes for the given array of ClassMetadataInfo instances
273      *
274      * @param array $metadatas
275      * @param string $outputDirectory
276      * @return void
277      */
278     public function generate(array $metadatas, $outputDirectory)
279     {
280         foreach ($metadatas as $metadata) {
281             $this->writeEntityClass($metadata, $outputDirectory);
282         }
283     }
284
285     /**
286      * Generated and write entity class to disk for the given ClassMetadataInfo instance
287      *
288      * @param ClassMetadataInfo $metadata
289      * @param string $outputDirectory
290      * @return void
291      */
292     public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
293     {
294         $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
295         $dir = dirname($path);
296
297         if ( ! is_dir($dir)) {
298             mkdir($dir, 0777, true);
299         }
300
301         $this->isNew = !file_exists($path) || (file_exists($path) && $this->regenerateEntityIfExists);
302
303         if ( ! $this->isNew) {
304             $this->parseTokensInEntityFile(file_get_contents($path));
305         } else {
306             $this->staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array());
307         }
308
309         if ($this->backupExisting && file_exists($path)) {
310             $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
311             if (!copy($path, $backupPath)) {
312                 throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
313             }
314         }
315
316         // If entity doesn't exist or we're re-generating the entities entirely
317         if ($this->isNew) {
318             file_put_contents($path, $this->generateEntityClass($metadata));
319         // If entity exists and we're allowed to update the entity class
320         } else if ( ! $this->isNew && $this->updateEntityIfExists) {
321             file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
322         }
323     }
324
325     /**
326      * Generate a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance
327      *
328      * @param ClassMetadataInfo $metadata
329      * @return string $code
330      */
331     public function generateEntityClass(ClassMetadataInfo $metadata)
332     {
333         $placeHolders = array(
334             '<namespace>',
335             '<entityAnnotation>',
336             '<entityClassName>',
337             '<entityBody>'
338         );
339
340         $replacements = array(
341             $this->generateEntityNamespace($metadata),
342             $this->generateEntityDocBlock($metadata),
343             $this->generateEntityClassName($metadata),
344             $this->generateEntityBody($metadata)
345         );
346
347         $code = str_replace($placeHolders, $replacements, self::$classTemplate);
348         return str_replace('<spaces>', $this->spaces, $code);
349     }
350
351     /**
352      * Generate the updated code for the given ClassMetadataInfo and entity at path
353      *
354      * @param ClassMetadataInfo $metadata
355      * @param string $path
356      * @return string $code;
357      */
358     public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
359     {
360         $currentCode = file_get_contents($path);
361
362         $body = $this->generateEntityBody($metadata);
363         $body = str_replace('<spaces>', $this->spaces, $body);
364         $last = strrpos($currentCode, '}');
365
366         return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : ''). "}";
367     }
368
369     /**
370      * Set the number of spaces the exported class should have
371      *
372      * @param integer $numSpaces
373      * @return void
374      */
375     public function setNumSpaces($numSpaces)
376     {
377         $this->spaces = str_repeat(' ', $numSpaces);
378         $this->numSpaces = $numSpaces;
379     }
380
381     /**
382      * Set the extension to use when writing php files to disk
383      *
384      * @param string $extension
385      * @return void
386      */
387     public function setExtension($extension)
388     {
389         $this->extension = $extension;
390     }
391
392     /**
393      * Set the name of the class the generated classes should extend from
394      *
395      * @return void
396      */
397     public function setClassToExtend($classToExtend)
398     {
399         $this->classToExtend = $classToExtend;
400     }
401
402     /**
403      * Set whether or not to generate annotations for the entity
404      *
405      * @param bool $bool
406      * @return void
407      */
408     public function setGenerateAnnotations($bool)
409     {
410         $this->generateAnnotations = $bool;
411     }
412
413     /**
414      * Set the class fields visibility for the entity (can either be private or protected)
415      *
416      * @param bool $bool
417      * @return void
418      */
419     public function setFieldVisibility($visibility)
420     {
421         if ($visibility !== self::FIELD_VISIBLE_PRIVATE && $visibility !== self::FIELD_VISIBLE_PROTECTED) {
422             throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility);
423         }
424
425         $this->fieldVisibility = $visibility;
426     }
427
428     /**
429      * Set an annotation prefix.
430      *
431      * @param string $prefix
432      */
433     public function setAnnotationPrefix($prefix)
434     {
435         $this->annotationsPrefix = $prefix;
436     }
437
438     /**
439      * Set whether or not to try and update the entity if it already exists
440      *
441      * @param bool $bool
442      * @return void
443      */
444     public function setUpdateEntityIfExists($bool)
445     {
446         $this->updateEntityIfExists = $bool;
447     }
448
449     /**
450      * Set whether or not to regenerate the entity if it exists
451      *
452      * @param bool $bool
453      * @return void
454      */
455     public function setRegenerateEntityIfExists($bool)
456     {
457         $this->regenerateEntityIfExists = $bool;
458     }
459
460     /**
461      * Set whether or not to generate stub methods for the entity
462      *
463      * @param bool $bool
464      * @return void
465      */
466     public function setGenerateStubMethods($bool)
467     {
468         $this->generateEntityStubMethods = $bool;
469     }
470
471     /**
472      * Should an existing entity be backed up if it already exists?
473      */
474     public function setBackupExisting($bool)
475     {
476         $this->backupExisting = $bool;
477     }
478
479      /**
480      * @param   string $type
481      * @return  string
482      */
483     private function getType($type)
484     {
485         if (isset($this->typeAlias[$type])) {
486             return $this->typeAlias[$type];
487         }
488
489         return $type;
490     }
491
492     private function generateEntityNamespace(ClassMetadataInfo $metadata)
493     {
494         if ($this->hasNamespace($metadata)) {
495             return 'namespace ' . $this->getNamespace($metadata) .';';
496         }
497     }
498
499     private function generateEntityClassName(ClassMetadataInfo $metadata)
500     {
501         return 'class ' . $this->getClassName($metadata) .
502             ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
503     }
504
505     private function generateEntityBody(ClassMetadataInfo $metadata)
506     {
507         $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
508         $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
509         $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
510         $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
511
512         $code = array();
513
514         if ($fieldMappingProperties) {
515             $code[] = $fieldMappingProperties;
516         }
517
518         if ($associationMappingProperties) {
519             $code[] = $associationMappingProperties;
520         }
521
522         $code[] = $this->generateEntityConstructor($metadata);
523
524         if ($stubMethods) {
525             $code[] = $stubMethods;
526         }
527
528         if ($lifecycleCallbackMethods) {
529             $code[] = $lifecycleCallbackMethods;
530         }
531
532         return implode("\n", $code);
533     }
534
535     private function generateEntityConstructor(ClassMetadataInfo $metadata)
536     {
537         if ($this->hasMethod('__construct', $metadata)) {
538             return '';
539         }
540
541         $collections = array();
542
543         foreach ($metadata->associationMappings as $mapping) {
544             if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
545                 $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
546             }
547         }
548
549         if ($collections) {
550             return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), self::$constructorMethodTemplate));
551         }
552
553         return '';
554     }
555
556     /**
557      * @todo this won't work if there is a namespace in brackets and a class outside of it.
558      * @param string $src
559      */
560     private function parseTokensInEntityFile($src)
561     {
562         $tokens = token_get_all($src);
563         $lastSeenNamespace = "";
564         $lastSeenClass = false;
565
566         $inNamespace = false;
567         $inClass = false;
568         for ($i = 0; $i < count($tokens); $i++) {
569             $token = $tokens[$i];
570             if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
571                 continue;
572             }
573
574             if ($inNamespace) {
575                 if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
576                     $lastSeenNamespace .= $token[1];
577                 } else if (is_string($token) && in_array($token, array(';', '{'))) {
578                     $inNamespace = false;
579                 }
580             }
581
582             if ($inClass) {
583                 $inClass = false;
584                 $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
585                 $this->staticReflection[$lastSeenClass]['properties'] = array();
586                 $this->staticReflection[$lastSeenClass]['methods'] = array();
587             }
588
589             if ($token[0] == T_NAMESPACE) {
590                 $lastSeenNamespace = "";
591                 $inNamespace = true;
592             } else if ($token[0] == T_CLASS) {
593                 $inClass = true;
594             } else if ($token[0] == T_FUNCTION) {
595                 if ($tokens[$i+2][0] == T_STRING) {
596                     $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1];
597                 } else if ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
598                     $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1];
599                 }
600             } else if (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
601                 $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
602             }
603         }
604     }
605
606     private function hasProperty($property, ClassMetadataInfo $metadata)
607     {
608         if ($this->extendsClass()) {
609             // don't generate property if its already on the base class.
610             $reflClass = new \ReflectionClass($this->getClassToExtend());
611             if ($reflClass->hasProperty($property)) {
612                 return true;
613             }
614         }
615
616         return (
617             isset($this->staticReflection[$metadata->name]) &&
618             in_array($property, $this->staticReflection[$metadata->name]['properties'])
619         );
620     }
621
622     private function hasMethod($method, ClassMetadataInfo $metadata)
623     {
624         if ($this->extendsClass()) {
625             // don't generate method if its already on the base class.
626             $reflClass = new \ReflectionClass($this->getClassToExtend());
627             if ($reflClass->hasMethod($method)) {
628                 return true;
629             }
630         }
631
632         return (
633             isset($this->staticReflection[$metadata->name]) &&
634             in_array($method, $this->staticReflection[$metadata->name]['methods'])
635         );
636     }
637
638     private function hasNamespace(ClassMetadataInfo $metadata)
639     {
640         return strpos($metadata->name, '\\') ? true : false;
641     }
642
643     private function extendsClass()
644     {
645         return $this->classToExtend ? true : false;
646     }
647
648     private function getClassToExtend()
649     {
650         return $this->classToExtend;
651     }
652
653     private function getClassToExtendName()
654     {
655         $refl = new \ReflectionClass($this->getClassToExtend());
656
657         return '\\' . $refl->getName();
658     }
659
660     private function getClassName(ClassMetadataInfo $metadata)
661     {
662         return ($pos = strrpos($metadata->name, '\\'))
663             ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
664     }
665
666     private function getNamespace(ClassMetadataInfo $metadata)
667     {
668         return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
669     }
670
671     private function generateEntityDocBlock(ClassMetadataInfo $metadata)
672     {
673         $lines = array();
674         $lines[] = '/**';
675         $lines[] = ' * '.$metadata->name;
676
677         if ($this->generateAnnotations) {
678             $lines[] = ' *';
679
680             $methods = array(
681                 'generateTableAnnotation',
682                 'generateInheritanceAnnotation',
683                 'generateDiscriminatorColumnAnnotation',
684                 'generateDiscriminatorMapAnnotation'
685             );
686
687             foreach ($methods as $method) {
688                 if ($code = $this->$method($metadata)) {
689                     $lines[] = ' * ' . $code;
690                 }
691             }
692
693             if ($metadata->isMappedSuperclass) {
694                 $lines[] = ' * @' . $this->annotationsPrefix . 'MappedSuperClass';
695             } else {
696                 $lines[] = ' * @' . $this->annotationsPrefix . 'Entity';
697             }
698
699             if ($metadata->customRepositoryClassName) {
700                 $lines[count($lines) - 1] .= '(repositoryClass="' . $metadata->customRepositoryClassName . '")';
701             }
702
703             if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
704                 $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
705             }
706         }
707
708         $lines[] = ' */';
709
710         return implode("\n", $lines);
711     }
712
713     private function generateTableAnnotation($metadata)
714     {
715         $table = array();
716
717         if (isset($metadata->table['schema'])) {
718             $table[] = 'schema="' . $metadata->table['schema'] . '"';
719         }
720
721         if (isset($metadata->table['name'])) {
722             $table[] = 'name="' . $metadata->table['name'] . '"';
723         }
724
725         if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
726             $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
727             $table[] = 'uniqueConstraints={' . $constraints . '}';
728         }
729
730         if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
731             $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
732             $table[] = 'indexes={' . $constraints . '}';
733         }
734
735         return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
736     }
737
738     private function generateTableConstraints($constraintName, $constraints)
739     {
740         $annotations = array();
741         foreach ($constraints as $name => $constraint) {
742             $columns = array();
743             foreach ($constraint['columns'] as $column) {
744                 $columns[] = '"' . $column . '"';
745             }
746             $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
747         }
748         return implode(', ', $annotations);
749     }
750
751     private function generateInheritanceAnnotation($metadata)
752     {
753         if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
754             return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
755         }
756     }
757
758     private function generateDiscriminatorColumnAnnotation($metadata)
759     {
760         if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
761             $discrColumn = $metadata->discriminatorValue;
762             $columnDefinition = 'name="' . $discrColumn['name']
763                 . '", type="' . $discrColumn['type']
764                 . '", length=' . $discrColumn['length'];
765
766             return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
767         }
768     }
769
770     private function generateDiscriminatorMapAnnotation($metadata)
771     {
772         if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
773             $inheritanceClassMap = array();
774
775             foreach ($metadata->discriminatorMap as $type => $class) {
776                 $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
777             }
778
779             return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
780         }
781     }
782
783     private function generateEntityStubMethods(ClassMetadataInfo $metadata)
784     {
785         $methods = array();
786
787         foreach ($metadata->fieldMappings as $fieldMapping) {
788             if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) {
789                 if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
790                     $methods[] = $code;
791                 }
792             }
793
794             if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
795                 $methods[] = $code;
796             }
797         }
798
799         foreach ($metadata->associationMappings as $associationMapping) {
800             if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
801                 $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
802                 if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
803                     $methods[] = $code;
804                 }
805                 if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
806                     $methods[] = $code;
807                 }
808             } else if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
809                 if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
810                     $methods[] = $code;
811                 }
812                 if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
813                     $methods[] = $code;
814                 }
815                 if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
816                     $methods[] = $code;
817                 }
818             }
819         }
820
821         return implode("\n\n", $methods);
822     }
823
824     private function isAssociationIsNullable($associationMapping)
825     {
826         if (isset($associationMapping['id']) && $associationMapping['id']) {
827             return false;
828         }
829         if (isset($associationMapping['joinColumns'])) {
830             $joinColumns = $associationMapping['joinColumns'];
831         } else {
832             //@todo thereis no way to retreive targetEntity metadata
833             $joinColumns = array();
834         }
835         foreach ($joinColumns as $joinColumn) {
836             if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
837                 return false;
838             }
839         }
840         return true;
841     }
842
843     private function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
844     {
845         if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
846             $methods = array();
847
848             foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
849                 foreach ($callbacks as $callback) {
850                     if ($code = $this->generateLifecycleCallbackMethod($name, $callback, $metadata)) {
851                         $methods[] = $code;
852                     }
853                 }
854             }
855
856             return implode("\n\n", $methods);
857         }
858
859         return "";
860     }
861
862     private function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
863     {
864         $lines = array();
865
866         foreach ($metadata->associationMappings as $associationMapping) {
867             if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
868                 continue;
869             }
870
871             $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
872             $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
873                      . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
874         }
875
876         return implode("\n", $lines);
877     }
878
879     private function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
880     {
881         $lines = array();
882
883         foreach ($metadata->fieldMappings as $fieldMapping) {
884             if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
885                 $metadata->isInheritedField($fieldMapping['fieldName'])) {
886                 continue;
887             }
888
889             $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
890             $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
891                      . (isset($fieldMapping['default']) ? ' = ' . var_export($fieldMapping['default'], true) : null) . ";\n";
892         }
893
894         return implode("\n", $lines);
895     }
896
897     private function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null,  $defaultValue = null)
898     {
899         $methodName = $type . Inflector::classify($fieldName);
900         if (in_array($type, array("add", "remove")) && substr($methodName, -1) == "s") {
901             $methodName = substr($methodName, 0, -1);
902         }
903
904         if ($this->hasMethod($methodName, $metadata)) {
905             return;
906         }
907         $this->staticReflection[$metadata->name]['methods'][] = $methodName;
908
909         $var = sprintf('%sMethodTemplate', $type);
910         $template = self::$$var;
911
912         $types          = Type::getTypesMap();
913         $variableType   = $typeHint ? $this->getType($typeHint) . ' ' : null;
914         $methodTypeHint = $typeHint && ! isset($types[$typeHint]) ? '\\' . $typeHint . ' ' : null;
915
916         $replacements = array(
917           '<description>'       => ucfirst($type) . ' ' . $fieldName,
918           '<methodTypeHint>'    => $methodTypeHint,
919           '<variableType>'      => $variableType,
920           '<variableName>'      => Inflector::camelize($fieldName),
921           '<methodName>'        => $methodName,
922           '<fieldName>'         => $fieldName,
923           '<variableDefault>'   => ($defaultValue !== null ) ? (' = '.$defaultValue) : '',
924           '<entity>'            => $this->getClassName($metadata)
925         );
926
927         $method = str_replace(
928             array_keys($replacements),
929             array_values($replacements),
930             $template
931         );
932
933         return $this->prefixCodeWithSpaces($method);
934     }
935
936     private function generateLifecycleCallbackMethod($name, $methodName, $metadata)
937     {
938         if ($this->hasMethod($methodName, $metadata)) {
939             return;
940         }
941         $this->staticReflection[$metadata->name]['methods'][] = $methodName;
942
943         $replacements = array(
944             '<name>'        => $this->annotationsPrefix . ucfirst($name),
945             '<methodName>'  => $methodName,
946         );
947
948         $method = str_replace(
949             array_keys($replacements),
950             array_values($replacements),
951             self::$lifecycleCallbackMethodTemplate
952         );
953
954         return $this->prefixCodeWithSpaces($method);
955     }
956
957     private function generateJoinColumnAnnotation(array $joinColumn)
958     {
959         $joinColumnAnnot = array();
960
961         if (isset($joinColumn['name'])) {
962             $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
963         }
964
965         if (isset($joinColumn['referencedColumnName'])) {
966             $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
967         }
968
969         if (isset($joinColumn['unique']) && $joinColumn['unique']) {
970             $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
971         }
972
973         if (isset($joinColumn['nullable'])) {
974             $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
975         }
976
977         if (isset($joinColumn['onDelete'])) {
978             $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
979         }
980
981         if (isset($joinColumn['columnDefinition'])) {
982             $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
983         }
984
985         return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
986     }
987
988     private function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
989     {
990         $lines = array();
991         $lines[] = $this->spaces . '/**';
992
993         if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
994             $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\ArrayCollection';
995         } else {
996             $lines[] = $this->spaces . ' * @var ' . $associationMapping['targetEntity'];
997         }
998
999         if ($this->generateAnnotations) {
1000             $lines[] = $this->spaces . ' *';
1001
1002             if (isset($associationMapping['id']) && $associationMapping['id']) {
1003                 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1004
1005                 if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1006                     $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1007                 }
1008             }
1009
1010             $type = null;
1011             switch ($associationMapping['type']) {
1012                 case ClassMetadataInfo::ONE_TO_ONE:
1013                     $type = 'OneToOne';
1014                     break;
1015                 case ClassMetadataInfo::MANY_TO_ONE:
1016                     $type = 'ManyToOne';
1017                     break;
1018                 case ClassMetadataInfo::ONE_TO_MANY:
1019                     $type = 'OneToMany';
1020                     break;
1021                 case ClassMetadataInfo::MANY_TO_MANY:
1022                     $type = 'ManyToMany';
1023                     break;
1024             }
1025             $typeOptions = array();
1026
1027             if (isset($associationMapping['targetEntity'])) {
1028                 $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1029             }
1030
1031             if (isset($associationMapping['inversedBy'])) {
1032                 $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1033             }
1034
1035             if (isset($associationMapping['mappedBy'])) {
1036                 $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1037             }
1038
1039             if ($associationMapping['cascade']) {
1040                 $cascades = array();
1041
1042                 if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1043                 if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1044                 if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1045                 if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1046                 if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1047
1048                 $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1049             }
1050
1051             if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1052                 $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1053             }
1054
1055             $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1056
1057             if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1058                 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1059
1060                 $joinColumnsLines = array();
1061
1062                 foreach ($associationMapping['joinColumns'] as $joinColumn) {
1063                     if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1064                         $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1065                     }
1066                 }
1067
1068                 $lines[] = implode(",\n", $joinColumnsLines);
1069                 $lines[] = $this->spaces . ' * })';
1070             }
1071
1072             if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1073                 $joinTable = array();
1074                 $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1075
1076                 if (isset($associationMapping['joinTable']['schema'])) {
1077                     $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1078                 }
1079
1080                 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1081                 $lines[] = $this->spaces . ' *   joinColumns={';
1082
1083                 foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
1084                     $lines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1085                 }
1086
1087                 $lines[] = $this->spaces . ' *   },';
1088                 $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1089
1090                 foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1091                     $lines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1092                 }
1093
1094                 $lines[] = $this->spaces . ' *   }';
1095                 $lines[] = $this->spaces . ' * )';
1096             }
1097
1098             if (isset($associationMapping['orderBy'])) {
1099                 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1100
1101                 foreach ($associationMapping['orderBy'] as $name => $direction) {
1102                     $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1103                 }
1104
1105                 $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1106                 $lines[] = $this->spaces . ' * })';
1107             }
1108         }
1109
1110         $lines[] = $this->spaces . ' */';
1111
1112         return implode("\n", $lines);
1113     }
1114
1115     private function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1116     {
1117         $lines = array();
1118         $lines[] = $this->spaces . '/**';
1119         $lines[] = $this->spaces . ' * @var ' . $this->getType($fieldMapping['type']) . ' $' . $fieldMapping['fieldName'];
1120
1121         if ($this->generateAnnotations) {
1122             $lines[] = $this->spaces . ' *';
1123
1124             $column = array();
1125             if (isset($fieldMapping['columnName'])) {
1126                 $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1127             }
1128
1129             if (isset($fieldMapping['type'])) {
1130                 $column[] = 'type="' . $fieldMapping['type'] . '"';
1131             }
1132
1133             if (isset($fieldMapping['length'])) {
1134                 $column[] = 'length=' . $fieldMapping['length'];
1135             }
1136
1137             if (isset($fieldMapping['precision'])) {
1138                 $column[] = 'precision=' .  $fieldMapping['precision'];
1139             }
1140
1141             if (isset($fieldMapping['scale'])) {
1142                 $column[] = 'scale=' . $fieldMapping['scale'];
1143             }
1144
1145             if (isset($fieldMapping['nullable'])) {
1146                 $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1147             }
1148
1149             if (isset($fieldMapping['columnDefinition'])) {
1150                 $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1151             }
1152
1153             if (isset($fieldMapping['unique'])) {
1154                 $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1155             }
1156
1157             $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1158
1159             if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1160                 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1161
1162                 if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1163                     $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1164                 }
1165
1166                 if ($metadata->sequenceGeneratorDefinition) {
1167                     $sequenceGenerator = array();
1168
1169                     if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1170                         $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1171                     }
1172
1173                     if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1174                         $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1175                     }
1176
1177                     if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1178                         $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1179                     }
1180
1181                     $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1182                 }
1183             }
1184
1185             if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1186                 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1187             }
1188         }
1189
1190         $lines[] = $this->spaces . ' */';
1191
1192         return implode("\n", $lines);
1193     }
1194
1195     private function prefixCodeWithSpaces($code, $num = 1)
1196     {
1197         $lines = explode("\n", $code);
1198
1199         foreach ($lines as $key => $value) {
1200             $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1201         }
1202
1203         return implode("\n", $lines);
1204     }
1205
1206     private function getInheritanceTypeString($type)
1207     {
1208         switch ($type) {
1209             case ClassMetadataInfo::INHERITANCE_TYPE_NONE:
1210                 return 'NONE';
1211
1212             case ClassMetadataInfo::INHERITANCE_TYPE_JOINED:
1213                 return 'JOINED';
1214
1215             case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE:
1216                 return 'SINGLE_TABLE';
1217
1218             case ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS:
1219                 return 'PER_CLASS';
1220
1221             default:
1222                 throw new \InvalidArgumentException('Invalid provided InheritanceType: ' . $type);
1223         }
1224     }
1225
1226     private function getChangeTrackingPolicyString($policy)
1227     {
1228         switch ($policy) {
1229             case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT:
1230                 return 'DEFERRED_IMPLICIT';
1231
1232             case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT:
1233                 return 'DEFERRED_EXPLICIT';
1234
1235             case ClassMetadataInfo::CHANGETRACKING_NOTIFY:
1236                 return 'NOTIFY';
1237
1238             default:
1239                 throw new \InvalidArgumentException('Invalid provided ChangeTrackingPolicy: ' . $policy);
1240         }
1241     }
1242
1243     private function getIdGeneratorTypeString($type)
1244     {
1245         switch ($type) {
1246             case ClassMetadataInfo::GENERATOR_TYPE_AUTO:
1247                 return 'AUTO';
1248
1249             case ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE:
1250                 return 'SEQUENCE';
1251
1252             case ClassMetadataInfo::GENERATOR_TYPE_TABLE:
1253                 return 'TABLE';
1254
1255             case ClassMetadataInfo::GENERATOR_TYPE_IDENTITY:
1256                 return 'IDENTITY';
1257
1258             case ClassMetadataInfo::GENERATOR_TYPE_NONE:
1259                 return 'NONE';
1260
1261             default:
1262                 throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
1263         }
1264     }
1265 }