EntityGenerator.php 39 KB

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