vendor/doctrine/lib/Doctrine/ORM/Tools/EntityGenerator.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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 LGPL. 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\ORM\Mapping\AssociationMapping,
       
    24     Doctrine\Common\Util\Inflector;
       
    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  * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
       
    40  * @link    www.doctrine-project.org
       
    41  * @since   2.0
       
    42  * @version $Revision$
       
    43  * @author  Benjamin Eberlei <kontakt@beberlei.de>
       
    44  * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
       
    45  * @author  Jonathan Wage <jonwage@gmail.com>
       
    46  * @author  Roman Borschel <roman@code-factory.org>
       
    47  */
       
    48 class EntityGenerator
       
    49 {
       
    50     /**
       
    51      * @var bool
       
    52      */
       
    53     private $_backupExisting = true;
       
    54 
       
    55     /** The extension to use for written php files */
       
    56     private $_extension = '.php';
       
    57 
       
    58     /** Whether or not the current ClassMetadataInfo instance is new or old */
       
    59     private $_isNew = true;
       
    60 
       
    61     private $_staticReflection = array();
       
    62 
       
    63     /** Number of spaces to use for indention in generated code */
       
    64     private $_numSpaces = 4;
       
    65 
       
    66     /** The actual spaces to use for indention */
       
    67     private $_spaces = '    ';
       
    68 
       
    69     /** The class all generated entities should extend */
       
    70     private $_classToExtend;
       
    71 
       
    72     /** Whether or not to generation annotations */
       
    73     private $_generateAnnotations = false;
       
    74 
       
    75     /**
       
    76      * @var string
       
    77      */
       
    78     private $_annotationsPrefix = '';
       
    79 
       
    80     /** Whether or not to generated sub methods */
       
    81     private $_generateEntityStubMethods = false;
       
    82 
       
    83     /** Whether or not to update the entity class if it exists already */
       
    84     private $_updateEntityIfExists = false;
       
    85 
       
    86     /** Whether or not to re-generate entity class if it exists already */
       
    87     private $_regenerateEntityIfExists = false;
       
    88 
       
    89     private static $_classTemplate =
       
    90 '<?php
       
    91 
       
    92 <namespace>
       
    93 
       
    94 use Doctrine\ORM\Mapping as ORM;
       
    95 
       
    96 <entityAnnotation>
       
    97 <entityClassName>
       
    98 {
       
    99 <entityBody>
       
   100 }';
       
   101 
       
   102     private static $_getMethodTemplate =
       
   103 '/**
       
   104  * <description>
       
   105  *
       
   106  * @return <variableType>
       
   107  */
       
   108 public function <methodName>()
       
   109 {
       
   110 <spaces>return $this-><fieldName>;
       
   111 }';
       
   112 
       
   113     private static $_setMethodTemplate =
       
   114 '/**
       
   115  * <description>
       
   116  *
       
   117  * @param <variableType>$<variableName>
       
   118  */
       
   119 public function <methodName>(<methodTypeHint>$<variableName>)
       
   120 {
       
   121 <spaces>$this-><fieldName> = $<variableName>;
       
   122 }';
       
   123 
       
   124     private static $_addMethodTemplate =
       
   125 '/**
       
   126  * <description>
       
   127  *
       
   128  * @param <variableType>$<variableName>
       
   129  */
       
   130 public function <methodName>(<methodTypeHint>$<variableName>)
       
   131 {
       
   132 <spaces>$this-><fieldName>[] = $<variableName>;
       
   133 }';
       
   134 
       
   135     private static $_lifecycleCallbackMethodTemplate =
       
   136 '/**
       
   137  * @<name>
       
   138  */
       
   139 public function <methodName>()
       
   140 {
       
   141 <spaces>// Add your code here
       
   142 }';
       
   143 
       
   144     private static $_constructorMethodTemplate =
       
   145 'public function __construct()
       
   146 {
       
   147 <spaces><collections>
       
   148 }
       
   149 ';
       
   150 
       
   151     public function __construct()
       
   152     {
       
   153         if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
       
   154             $this->_annotationsPrefix = 'ORM\\';
       
   155         }
       
   156     }
       
   157 
       
   158     /**
       
   159      * Generate and write entity classes for the given array of ClassMetadataInfo instances
       
   160      *
       
   161      * @param array $metadatas
       
   162      * @param string $outputDirectory
       
   163      * @return void
       
   164      */
       
   165     public function generate(array $metadatas, $outputDirectory)
       
   166     {
       
   167         foreach ($metadatas as $metadata) {
       
   168             $this->writeEntityClass($metadata, $outputDirectory);
       
   169         }
       
   170     }
       
   171 
       
   172     /**
       
   173      * Generated and write entity class to disk for the given ClassMetadataInfo instance
       
   174      *
       
   175      * @param ClassMetadataInfo $metadata
       
   176      * @param string $outputDirectory
       
   177      * @return void
       
   178      */
       
   179     public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
       
   180     {
       
   181         $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->_extension;
       
   182         $dir = dirname($path);
       
   183 
       
   184         if ( ! is_dir($dir)) {
       
   185             mkdir($dir, 0777, true);
       
   186         }
       
   187 
       
   188         $this->_isNew = !file_exists($path) || (file_exists($path) && $this->_regenerateEntityIfExists);
       
   189 
       
   190         if ( ! $this->_isNew) {
       
   191             $this->_parseTokensInEntityFile(file_get_contents($path));
       
   192         }
       
   193 
       
   194         if ($this->_backupExisting && file_exists($path)) {
       
   195             $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
       
   196             if (!copy($path, $backupPath)) {
       
   197                 throw new \RuntimeException("Attempt to backup overwritten entitiy file but copy operation failed.");
       
   198             }
       
   199         }
       
   200 
       
   201         // If entity doesn't exist or we're re-generating the entities entirely
       
   202         if ($this->_isNew) {
       
   203             file_put_contents($path, $this->generateEntityClass($metadata));
       
   204         // If entity exists and we're allowed to update the entity class
       
   205         } else if ( ! $this->_isNew && $this->_updateEntityIfExists) {
       
   206             file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
       
   207         }
       
   208     }
       
   209 
       
   210     /**
       
   211      * Generate a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance
       
   212      *
       
   213      * @param ClassMetadataInfo $metadata
       
   214      * @return string $code
       
   215      */
       
   216     public function generateEntityClass(ClassMetadataInfo $metadata)
       
   217     {
       
   218         $placeHolders = array(
       
   219             '<namespace>',
       
   220             '<entityAnnotation>',
       
   221             '<entityClassName>',
       
   222             '<entityBody>'
       
   223         );
       
   224 
       
   225         $replacements = array(
       
   226             $this->_generateEntityNamespace($metadata),
       
   227             $this->_generateEntityDocBlock($metadata),
       
   228             $this->_generateEntityClassName($metadata),
       
   229             $this->_generateEntityBody($metadata)
       
   230         );
       
   231 
       
   232         $code = str_replace($placeHolders, $replacements, self::$_classTemplate);
       
   233         return str_replace('<spaces>', $this->_spaces, $code);
       
   234     }
       
   235 
       
   236     /**
       
   237      * Generate the updated code for the given ClassMetadataInfo and entity at path
       
   238      *
       
   239      * @param ClassMetadataInfo $metadata
       
   240      * @param string $path
       
   241      * @return string $code;
       
   242      */
       
   243     public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
       
   244     {
       
   245         $currentCode = file_get_contents($path);
       
   246 
       
   247         $body = $this->_generateEntityBody($metadata);
       
   248         $body = str_replace('<spaces>', $this->_spaces, $body);
       
   249         $last = strrpos($currentCode, '}');
       
   250 
       
   251         return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : ''). "}";
       
   252     }
       
   253 
       
   254     /**
       
   255      * Set the number of spaces the exported class should have
       
   256      *
       
   257      * @param integer $numSpaces
       
   258      * @return void
       
   259      */
       
   260     public function setNumSpaces($numSpaces)
       
   261     {
       
   262         $this->_spaces = str_repeat(' ', $numSpaces);
       
   263         $this->_numSpaces = $numSpaces;
       
   264     }
       
   265 
       
   266     /**
       
   267      * Set the extension to use when writing php files to disk
       
   268      *
       
   269      * @param string $extension
       
   270      * @return void
       
   271      */
       
   272     public function setExtension($extension)
       
   273     {
       
   274         $this->_extension = $extension;
       
   275     }
       
   276 
       
   277     /**
       
   278      * Set the name of the class the generated classes should extend from
       
   279      *
       
   280      * @return void
       
   281      */
       
   282     public function setClassToExtend($classToExtend)
       
   283     {
       
   284         $this->_classToExtend = $classToExtend;
       
   285     }
       
   286 
       
   287     /**
       
   288      * Set whether or not to generate annotations for the entity
       
   289      *
       
   290      * @param bool $bool
       
   291      * @return void
       
   292      */
       
   293     public function setGenerateAnnotations($bool)
       
   294     {
       
   295         $this->_generateAnnotations = $bool;
       
   296     }
       
   297 
       
   298     /**
       
   299      * Set an annotation prefix.
       
   300      *
       
   301      * @param string $prefix
       
   302      */
       
   303     public function setAnnotationPrefix($prefix)
       
   304     {
       
   305         if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
       
   306             return;
       
   307         }
       
   308         $this->_annotationsPrefix = $prefix;
       
   309     }
       
   310 
       
   311     /**
       
   312      * Set whether or not to try and update the entity if it already exists
       
   313      *
       
   314      * @param bool $bool
       
   315      * @return void
       
   316      */
       
   317     public function setUpdateEntityIfExists($bool)
       
   318     {
       
   319         $this->_updateEntityIfExists = $bool;
       
   320     }
       
   321 
       
   322     /**
       
   323      * Set whether or not to regenerate the entity if it exists
       
   324      *
       
   325      * @param bool $bool
       
   326      * @return void
       
   327      */
       
   328     public function setRegenerateEntityIfExists($bool)
       
   329     {
       
   330         $this->_regenerateEntityIfExists = $bool;
       
   331     }
       
   332 
       
   333     /**
       
   334      * Set whether or not to generate stub methods for the entity
       
   335      *
       
   336      * @param bool $bool
       
   337      * @return void
       
   338      */
       
   339     public function setGenerateStubMethods($bool)
       
   340     {
       
   341         $this->_generateEntityStubMethods = $bool;
       
   342     }
       
   343 
       
   344     /**
       
   345      * Should an existing entity be backed up if it already exists?
       
   346      */
       
   347     public function setBackupExisting($bool)
       
   348     {
       
   349         $this->_backupExisting = $bool;
       
   350     }
       
   351 
       
   352     private function _generateEntityNamespace(ClassMetadataInfo $metadata)
       
   353     {
       
   354         if ($this->_hasNamespace($metadata)) {
       
   355             return 'namespace ' . $this->_getNamespace($metadata) .';';
       
   356         }
       
   357     }
       
   358 
       
   359     private function _generateEntityClassName(ClassMetadataInfo $metadata)
       
   360     {
       
   361         return 'class ' . $this->_getClassName($metadata) .
       
   362             ($this->_extendsClass() ? ' extends ' . $this->_getClassToExtendName() : null);
       
   363     }
       
   364 
       
   365     private function _generateEntityBody(ClassMetadataInfo $metadata)
       
   366     {
       
   367         $fieldMappingProperties = $this->_generateEntityFieldMappingProperties($metadata);
       
   368         $associationMappingProperties = $this->_generateEntityAssociationMappingProperties($metadata);
       
   369         $stubMethods = $this->_generateEntityStubMethods ? $this->_generateEntityStubMethods($metadata) : null;
       
   370         $lifecycleCallbackMethods = $this->_generateEntityLifecycleCallbackMethods($metadata);
       
   371 
       
   372         $code = array();
       
   373 
       
   374         if ($fieldMappingProperties) {
       
   375             $code[] = $fieldMappingProperties;
       
   376         }
       
   377 
       
   378         if ($associationMappingProperties) {
       
   379             $code[] = $associationMappingProperties;
       
   380         }
       
   381 
       
   382         $code[] = $this->_generateEntityConstructor($metadata);
       
   383 
       
   384         if ($stubMethods) {
       
   385             $code[] = $stubMethods;
       
   386         }
       
   387 
       
   388         if ($lifecycleCallbackMethods) {
       
   389             $code[] = $lifecycleCallbackMethods;
       
   390         }
       
   391 
       
   392         return implode("\n", $code);
       
   393     }
       
   394 
       
   395     private function _generateEntityConstructor(ClassMetadataInfo $metadata)
       
   396     {
       
   397         if ($this->_hasMethod('__construct', $metadata)) {
       
   398             return '';
       
   399         }
       
   400 
       
   401         $collections = array();
       
   402         foreach ($metadata->associationMappings AS $mapping) {
       
   403             if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
       
   404                 $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
       
   405             }
       
   406         }
       
   407         if ($collections) {
       
   408             return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate));
       
   409         }
       
   410         return '';
       
   411     }
       
   412 
       
   413     /**
       
   414      * @todo this won't work if there is a namespace in brackets and a class outside of it.
       
   415      * @param string $src
       
   416      */
       
   417     private function _parseTokensInEntityFile($src)
       
   418     {
       
   419         $tokens = token_get_all($src);
       
   420         $lastSeenNamespace = "";
       
   421         $lastSeenClass = false;
       
   422 
       
   423         $inNamespace = false;
       
   424         $inClass = false;
       
   425         for ($i = 0; $i < count($tokens); $i++) {
       
   426             $token = $tokens[$i];
       
   427             if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
       
   428                 continue;
       
   429             }
       
   430 
       
   431             if ($inNamespace) {
       
   432                 if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
       
   433                     $lastSeenNamespace .= $token[1];
       
   434                 } else if (is_string($token) && in_array($token, array(';', '{'))) {
       
   435                     $inNamespace = false;
       
   436                 }
       
   437             }
       
   438 
       
   439             if ($inClass) {
       
   440                 $inClass = false;
       
   441                 $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
       
   442                 $this->_staticReflection[$lastSeenClass]['properties'] = array();
       
   443                 $this->_staticReflection[$lastSeenClass]['methods'] = array();
       
   444             }
       
   445 
       
   446             if ($token[0] == T_NAMESPACE) {
       
   447                 $lastSeenNamespace = "";
       
   448                 $inNamespace = true;
       
   449             } else if ($token[0] == T_CLASS) {
       
   450                 $inClass = true;
       
   451             } else if ($token[0] == T_FUNCTION) {
       
   452                 if ($tokens[$i+2][0] == T_STRING) {
       
   453                     $this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1];
       
   454                 } else if ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
       
   455                     $this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1];
       
   456                 }
       
   457             } else if (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
       
   458                 $this->_staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
       
   459             }
       
   460         }
       
   461     }
       
   462 
       
   463     private function _hasProperty($property, ClassMetadataInfo $metadata)
       
   464     {
       
   465         if ($this->_extendsClass()) {
       
   466             // don't generate property if its already on the base class.
       
   467             $reflClass = new \ReflectionClass($this->_getClassToExtend());
       
   468             if ($reflClass->hasProperty($property)) {
       
   469                 return true;
       
   470             }
       
   471         }
       
   472 
       
   473         return (
       
   474             isset($this->_staticReflection[$metadata->name]) &&
       
   475             in_array($property, $this->_staticReflection[$metadata->name]['properties'])
       
   476         );
       
   477     }
       
   478 
       
   479     private function _hasMethod($method, ClassMetadataInfo $metadata)
       
   480     {
       
   481         if ($this->_extendsClass()) {
       
   482             // don't generate method if its already on the base class.
       
   483             $reflClass = new \ReflectionClass($this->_getClassToExtend());
       
   484             if ($reflClass->hasMethod($method)) {
       
   485                 return true;
       
   486             }
       
   487         }
       
   488 
       
   489         return (
       
   490             isset($this->_staticReflection[$metadata->name]) &&
       
   491             in_array($method, $this->_staticReflection[$metadata->name]['methods'])
       
   492         );
       
   493     }
       
   494 
       
   495     private function _hasNamespace(ClassMetadataInfo $metadata)
       
   496     {
       
   497         return strpos($metadata->name, '\\') ? true : false;
       
   498     }
       
   499 
       
   500     private function _extendsClass()
       
   501     {
       
   502         return $this->_classToExtend ? true : false;
       
   503     }
       
   504 
       
   505     private function _getClassToExtend()
       
   506     {
       
   507         return $this->_classToExtend;
       
   508     }
       
   509 
       
   510     private function _getClassToExtendName()
       
   511     {
       
   512         $refl = new \ReflectionClass($this->_getClassToExtend());
       
   513 
       
   514         return '\\' . $refl->getName();
       
   515     }
       
   516 
       
   517     private function _getClassName(ClassMetadataInfo $metadata)
       
   518     {
       
   519         return ($pos = strrpos($metadata->name, '\\'))
       
   520             ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
       
   521     }
       
   522 
       
   523     private function _getNamespace(ClassMetadataInfo $metadata)
       
   524     {
       
   525         return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
       
   526     }
       
   527 
       
   528     private function _generateEntityDocBlock(ClassMetadataInfo $metadata)
       
   529     {
       
   530         $lines = array();
       
   531         $lines[] = '/**';
       
   532         $lines[] = ' * '.$metadata->name;
       
   533 
       
   534         if ($this->_generateAnnotations) {
       
   535             $lines[] = ' *';
       
   536 
       
   537             $methods = array(
       
   538                 '_generateTableAnnotation',
       
   539                 '_generateInheritanceAnnotation',
       
   540                 '_generateDiscriminatorColumnAnnotation',
       
   541                 '_generateDiscriminatorMapAnnotation'
       
   542             );
       
   543 
       
   544             foreach ($methods as $method) {
       
   545                 if ($code = $this->$method($metadata)) {
       
   546                     $lines[] = ' * ' . $code;
       
   547                 }
       
   548             }
       
   549 
       
   550             if ($metadata->isMappedSuperclass) {
       
   551                 $lines[] = ' * @' . $this->_annotationsPrefix . 'MappedSuperClass';
       
   552             } else {
       
   553                 $lines[] = ' * @' . $this->_annotationsPrefix . 'Entity';
       
   554             }
       
   555 
       
   556             if ($metadata->customRepositoryClassName) {
       
   557                 $lines[count($lines) - 1] .= '(repositoryClass="' . $metadata->customRepositoryClassName . '")';
       
   558             }
       
   559 
       
   560             if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
       
   561                 $lines[] = ' * @' . $this->_annotationsPrefix . 'HasLifecycleCallbacks';
       
   562             }
       
   563         }
       
   564 
       
   565         $lines[] = ' */';
       
   566 
       
   567         return implode("\n", $lines);
       
   568     }
       
   569 
       
   570     private function _generateTableAnnotation($metadata)
       
   571     {
       
   572         $table = array();
       
   573         if ($metadata->table['name']) {
       
   574             $table[] = 'name="' . $metadata->table['name'] . '"';
       
   575         }
       
   576 
       
   577         return '@' . $this->_annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
       
   578     }
       
   579 
       
   580     private function _generateInheritanceAnnotation($metadata)
       
   581     {
       
   582         if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
       
   583             return '@' . $this->_annotationsPrefix . 'InheritanceType("'.$this->_getInheritanceTypeString($metadata->inheritanceType).'")';
       
   584         }
       
   585     }
       
   586 
       
   587     private function _generateDiscriminatorColumnAnnotation($metadata)
       
   588     {
       
   589         if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
       
   590             $discrColumn = $metadata->discriminatorValue;
       
   591             $columnDefinition = 'name="' . $discrColumn['name']
       
   592                 . '", type="' . $discrColumn['type']
       
   593                 . '", length=' . $discrColumn['length'];
       
   594 
       
   595             return '@' . $this->_annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
       
   596         }
       
   597     }
       
   598 
       
   599     private function _generateDiscriminatorMapAnnotation($metadata)
       
   600     {
       
   601         if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
       
   602             $inheritanceClassMap = array();
       
   603 
       
   604             foreach ($metadata->discriminatorMap as $type => $class) {
       
   605                 $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
       
   606             }
       
   607 
       
   608             return '@' . $this->_annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
       
   609         }
       
   610     }
       
   611 
       
   612     private function _generateEntityStubMethods(ClassMetadataInfo $metadata)
       
   613     {
       
   614         $methods = array();
       
   615 
       
   616         foreach ($metadata->fieldMappings as $fieldMapping) {
       
   617             if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) {
       
   618                 if ($code = $this->_generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
       
   619                     $methods[] = $code;
       
   620                 }
       
   621             }
       
   622 
       
   623             if ($code = $this->_generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
       
   624                 $methods[] = $code;
       
   625             }
       
   626         }
       
   627 
       
   628         foreach ($metadata->associationMappings as $associationMapping) {
       
   629             if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
       
   630                 if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
       
   631                     $methods[] = $code;
       
   632                 }
       
   633                 if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
       
   634                     $methods[] = $code;
       
   635                 }
       
   636             } else if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
       
   637                 if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
       
   638                     $methods[] = $code;
       
   639                 }
       
   640                 if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
       
   641                     $methods[] = $code;
       
   642                 }
       
   643             }
       
   644         }
       
   645 
       
   646         return implode("\n\n", $methods);
       
   647     }
       
   648 
       
   649     private function _generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
       
   650     {
       
   651         if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
       
   652             $methods = array();
       
   653 
       
   654             foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
       
   655                 foreach ($callbacks as $callback) {
       
   656                     if ($code = $this->_generateLifecycleCallbackMethod($name, $callback, $metadata)) {
       
   657                         $methods[] = $code;
       
   658                     }
       
   659                 }
       
   660             }
       
   661 
       
   662             return implode("\n\n", $methods);
       
   663         }
       
   664 
       
   665         return "";
       
   666     }
       
   667 
       
   668     private function _generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
       
   669     {
       
   670         $lines = array();
       
   671 
       
   672         foreach ($metadata->associationMappings as $associationMapping) {
       
   673             if ($this->_hasProperty($associationMapping['fieldName'], $metadata)) {
       
   674                 continue;
       
   675             }
       
   676 
       
   677             $lines[] = $this->_generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
       
   678             $lines[] = $this->_spaces . 'private $' . $associationMapping['fieldName']
       
   679                      . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
       
   680         }
       
   681 
       
   682         return implode("\n", $lines);
       
   683     }
       
   684 
       
   685     private function _generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
       
   686     {
       
   687         $lines = array();
       
   688 
       
   689         foreach ($metadata->fieldMappings as $fieldMapping) {
       
   690             if ($this->_hasProperty($fieldMapping['fieldName'], $metadata) ||
       
   691                 $metadata->isInheritedField($fieldMapping['fieldName'])) {
       
   692                 continue;
       
   693             }
       
   694 
       
   695             $lines[] = $this->_generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
       
   696             $lines[] = $this->_spaces . 'private $' . $fieldMapping['fieldName']
       
   697                      . (isset($fieldMapping['default']) ? ' = ' . var_export($fieldMapping['default'], true) : null) . ";\n";
       
   698         }
       
   699 
       
   700         return implode("\n", $lines);
       
   701     }
       
   702 
       
   703     private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null)
       
   704     {
       
   705         if ($type == "add") {
       
   706             $addMethod = explode("\\", $typeHint);
       
   707             $addMethod = end($addMethod);
       
   708             $methodName = $type . $addMethod;
       
   709         } else {
       
   710             $methodName = $type . Inflector::classify($fieldName);
       
   711         }
       
   712 
       
   713         if ($this->_hasMethod($methodName, $metadata)) {
       
   714             return;
       
   715         }
       
   716         $this->_staticReflection[$metadata->name]['methods'][] = $methodName;
       
   717 
       
   718         $var = sprintf('_%sMethodTemplate', $type);
       
   719         $template = self::$$var;
       
   720 
       
   721         $variableType = $typeHint ? $typeHint . ' ' : null;
       
   722 
       
   723         $types = \Doctrine\DBAL\Types\Type::getTypesMap();
       
   724         $methodTypeHint = $typeHint && ! isset($types[$typeHint]) ? '\\' . $typeHint . ' ' : null;
       
   725 
       
   726         $replacements = array(
       
   727           '<description>'       => ucfirst($type) . ' ' . $fieldName,
       
   728           '<methodTypeHint>'    => $methodTypeHint,
       
   729           '<variableType>'      => $variableType,
       
   730           '<variableName>'      => Inflector::camelize($fieldName),
       
   731           '<methodName>'        => $methodName,
       
   732           '<fieldName>'         => $fieldName
       
   733         );
       
   734 
       
   735         $method = str_replace(
       
   736             array_keys($replacements),
       
   737             array_values($replacements),
       
   738             $template
       
   739         );
       
   740 
       
   741         return $this->_prefixCodeWithSpaces($method);
       
   742     }
       
   743 
       
   744     private function _generateLifecycleCallbackMethod($name, $methodName, $metadata)
       
   745     {
       
   746         if ($this->_hasMethod($methodName, $metadata)) {
       
   747             return;
       
   748         }
       
   749         $this->_staticReflection[$metadata->name]['methods'][] = $methodName;
       
   750 
       
   751         $replacements = array(
       
   752             '<name>'        => $this->_annotationsPrefix . $name,
       
   753             '<methodName>'  => $methodName,
       
   754         );
       
   755 
       
   756         $method = str_replace(
       
   757             array_keys($replacements),
       
   758             array_values($replacements),
       
   759             self::$_lifecycleCallbackMethodTemplate
       
   760         );
       
   761 
       
   762         return $this->_prefixCodeWithSpaces($method);
       
   763     }
       
   764 
       
   765     private function _generateJoinColumnAnnotation(array $joinColumn)
       
   766     {
       
   767         $joinColumnAnnot = array();
       
   768 
       
   769         if (isset($joinColumn['name'])) {
       
   770             $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
       
   771         }
       
   772 
       
   773         if (isset($joinColumn['referencedColumnName'])) {
       
   774             $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
       
   775         }
       
   776 
       
   777         if (isset($joinColumn['unique']) && $joinColumn['unique']) {
       
   778             $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
       
   779         }
       
   780 
       
   781         if (isset($joinColumn['nullable'])) {
       
   782             $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
       
   783         }
       
   784 
       
   785         if (isset($joinColumn['onDelete'])) {
       
   786             $joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false');
       
   787         }
       
   788 
       
   789         if (isset($joinColumn['onUpdate'])) {
       
   790             $joinColumnAnnot[] = 'onUpdate=' . ($joinColumn['onUpdate'] ? 'true' : 'false');
       
   791         }
       
   792 
       
   793         if (isset($joinColumn['columnDefinition'])) {
       
   794             $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
       
   795         }
       
   796 
       
   797         return '@' . $this->_annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
       
   798     }
       
   799 
       
   800     private function _generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
       
   801     {
       
   802         $lines = array();
       
   803         $lines[] = $this->_spaces . '/**';
       
   804         $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity'];
       
   805 
       
   806         if ($this->_generateAnnotations) {
       
   807             $lines[] = $this->_spaces . ' *';
       
   808 
       
   809             $type = null;
       
   810             switch ($associationMapping['type']) {
       
   811                 case ClassMetadataInfo::ONE_TO_ONE:
       
   812                     $type = 'OneToOne';
       
   813                     break;
       
   814                 case ClassMetadataInfo::MANY_TO_ONE:
       
   815                     $type = 'ManyToOne';
       
   816                     break;
       
   817                 case ClassMetadataInfo::ONE_TO_MANY:
       
   818                     $type = 'OneToMany';
       
   819                     break;
       
   820                 case ClassMetadataInfo::MANY_TO_MANY:
       
   821                     $type = 'ManyToMany';
       
   822                     break;
       
   823             }
       
   824             $typeOptions = array();
       
   825 
       
   826             if (isset($associationMapping['targetEntity'])) {
       
   827                 $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
       
   828             }
       
   829 
       
   830             if (isset($associationMapping['inversedBy'])) {
       
   831                 $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
       
   832             }
       
   833 
       
   834             if (isset($associationMapping['mappedBy'])) {
       
   835                 $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
       
   836             }
       
   837 
       
   838             if ($associationMapping['cascade']) {
       
   839                 $cascades = array();
       
   840 
       
   841                 if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
       
   842                 if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
       
   843                 if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
       
   844                 if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
       
   845                 if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
       
   846 
       
   847                 $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
       
   848             }
       
   849 
       
   850             if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
       
   851                 $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
       
   852             }
       
   853 
       
   854             $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
       
   855 
       
   856             if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
       
   857                 $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinColumns({';
       
   858 
       
   859                 $joinColumnsLines = array();
       
   860 
       
   861                 foreach ($associationMapping['joinColumns'] as $joinColumn) {
       
   862                     if ($joinColumnAnnot = $this->_generateJoinColumnAnnotation($joinColumn)) {
       
   863                         $joinColumnsLines[] = $this->_spaces . ' *   ' . $joinColumnAnnot;
       
   864                     }
       
   865                 }
       
   866 
       
   867                 $lines[] = implode(",\n", $joinColumnsLines);
       
   868                 $lines[] = $this->_spaces . ' * })';
       
   869             }
       
   870 
       
   871             if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
       
   872                 $joinTable = array();
       
   873                 $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
       
   874 
       
   875                 if (isset($associationMapping['joinTable']['schema'])) {
       
   876                     $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
       
   877                 }
       
   878 
       
   879                 $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
       
   880                 $lines[] = $this->_spaces . ' *   joinColumns={';
       
   881 
       
   882                 foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
       
   883                     $lines[] = $this->_spaces . ' *     ' . $this->_generateJoinColumnAnnotation($joinColumn);
       
   884                 }
       
   885 
       
   886                 $lines[] = $this->_spaces . ' *   },';
       
   887                 $lines[] = $this->_spaces . ' *   inverseJoinColumns={';
       
   888 
       
   889                 foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
       
   890                     $lines[] = $this->_spaces . ' *     ' . $this->_generateJoinColumnAnnotation($joinColumn);
       
   891                 }
       
   892 
       
   893                 $lines[] = $this->_spaces . ' *   }';
       
   894                 $lines[] = $this->_spaces . ' * )';
       
   895             }
       
   896 
       
   897             if (isset($associationMapping['orderBy'])) {
       
   898                 $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'OrderBy({';
       
   899 
       
   900                 foreach ($associationMapping['orderBy'] as $name => $direction) {
       
   901                     $lines[] = $this->_spaces . ' *     "' . $name . '"="' . $direction . '",';
       
   902                 }
       
   903 
       
   904                 $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
       
   905                 $lines[] = $this->_spaces . ' * })';
       
   906             }
       
   907         }
       
   908 
       
   909         $lines[] = $this->_spaces . ' */';
       
   910 
       
   911         return implode("\n", $lines);
       
   912     }
       
   913 
       
   914     private function _generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
       
   915     {
       
   916         $lines = array();
       
   917         $lines[] = $this->_spaces . '/**';
       
   918         $lines[] = $this->_spaces . ' * @var ' . $fieldMapping['type'] . ' $' . $fieldMapping['fieldName'];
       
   919 
       
   920         if ($this->_generateAnnotations) {
       
   921             $lines[] = $this->_spaces . ' *';
       
   922 
       
   923             $column = array();
       
   924             if (isset($fieldMapping['columnName'])) {
       
   925                 $column[] = 'name="' . $fieldMapping['columnName'] . '"';
       
   926             }
       
   927 
       
   928             if (isset($fieldMapping['type'])) {
       
   929                 $column[] = 'type="' . $fieldMapping['type'] . '"';
       
   930             }
       
   931 
       
   932             if (isset($fieldMapping['length'])) {
       
   933                 $column[] = 'length=' . $fieldMapping['length'];
       
   934             }
       
   935 
       
   936             if (isset($fieldMapping['precision'])) {
       
   937                 $column[] = 'precision=' .  $fieldMapping['precision'];
       
   938             }
       
   939 
       
   940             if (isset($fieldMapping['scale'])) {
       
   941                 $column[] = 'scale=' . $fieldMapping['scale'];
       
   942             }
       
   943 
       
   944             if (isset($fieldMapping['nullable'])) {
       
   945                 $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
       
   946             }
       
   947 
       
   948             if (isset($fieldMapping['columnDefinition'])) {
       
   949                 $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
       
   950             }
       
   951 
       
   952             if (isset($fieldMapping['unique'])) {
       
   953                 $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
       
   954             }
       
   955 
       
   956             $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
       
   957 
       
   958             if (isset($fieldMapping['id']) && $fieldMapping['id']) {
       
   959                 $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Id';
       
   960 
       
   961                 if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
       
   962                     $lines[] = $this->_spaces.' * @' . $this->_annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
       
   963                 }
       
   964 
       
   965                 if ($metadata->sequenceGeneratorDefinition) {
       
   966                     $sequenceGenerator = array();
       
   967 
       
   968                     if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
       
   969                         $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
       
   970                     }
       
   971 
       
   972                     if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
       
   973                         $sequenceGenerator[] = 'allocationSize="' . $metadata->sequenceGeneratorDefinition['allocationSize'] . '"';
       
   974                     }
       
   975 
       
   976                     if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
       
   977                         $sequenceGenerator[] = 'initialValue="' . $metadata->sequenceGeneratorDefinition['initialValue'] . '"';
       
   978                     }
       
   979 
       
   980                     $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
       
   981                 }
       
   982             }
       
   983 
       
   984             if (isset($fieldMapping['version']) && $fieldMapping['version']) {
       
   985                 $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Version';
       
   986             }
       
   987         }
       
   988 
       
   989         $lines[] = $this->_spaces . ' */';
       
   990 
       
   991         return implode("\n", $lines);
       
   992     }
       
   993 
       
   994     private function _prefixCodeWithSpaces($code, $num = 1)
       
   995     {
       
   996         $lines = explode("\n", $code);
       
   997 
       
   998         foreach ($lines as $key => $value) {
       
   999             $lines[$key] = str_repeat($this->_spaces, $num) . $lines[$key];
       
  1000         }
       
  1001 
       
  1002         return implode("\n", $lines);
       
  1003     }
       
  1004 
       
  1005     private function _getInheritanceTypeString($type)
       
  1006     {
       
  1007         switch ($type) {
       
  1008             case ClassMetadataInfo::INHERITANCE_TYPE_NONE:
       
  1009                 return 'NONE';
       
  1010 
       
  1011             case ClassMetadataInfo::INHERITANCE_TYPE_JOINED:
       
  1012                 return 'JOINED';
       
  1013 
       
  1014             case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE:
       
  1015                 return 'SINGLE_TABLE';
       
  1016 
       
  1017             case ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS:
       
  1018                 return 'PER_CLASS';
       
  1019 
       
  1020             default:
       
  1021                 throw new \InvalidArgumentException('Invalid provided InheritanceType: ' . $type);
       
  1022         }
       
  1023     }
       
  1024 
       
  1025     private function _getChangeTrackingPolicyString($policy)
       
  1026     {
       
  1027         switch ($policy) {
       
  1028             case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT:
       
  1029                 return 'DEFERRED_IMPLICIT';
       
  1030 
       
  1031             case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT:
       
  1032                 return 'DEFERRED_EXPLICIT';
       
  1033 
       
  1034             case ClassMetadataInfo::CHANGETRACKING_NOTIFY:
       
  1035                 return 'NOTIFY';
       
  1036 
       
  1037             default:
       
  1038                 throw new \InvalidArgumentException('Invalid provided ChangeTrackingPolicy: ' . $policy);
       
  1039         }
       
  1040     }
       
  1041 
       
  1042     private function _getIdGeneratorTypeString($type)
       
  1043     {
       
  1044         switch ($type) {
       
  1045             case ClassMetadataInfo::GENERATOR_TYPE_AUTO:
       
  1046                 return 'AUTO';
       
  1047 
       
  1048             case ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE:
       
  1049                 return 'SEQUENCE';
       
  1050 
       
  1051             case ClassMetadataInfo::GENERATOR_TYPE_TABLE:
       
  1052                 return 'TABLE';
       
  1053 
       
  1054             case ClassMetadataInfo::GENERATOR_TYPE_IDENTITY:
       
  1055                 return 'IDENTITY';
       
  1056 
       
  1057             case ClassMetadataInfo::GENERATOR_TYPE_NONE:
       
  1058                 return 'NONE';
       
  1059 
       
  1060             default:
       
  1061                 throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
       
  1062         }
       
  1063     }
       
  1064 }