EntityGenerator.php 36KB

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