EntityGenerator.php 36KB

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