EntityGenerator.php 39 KB

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