PageRenderTime 46ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Propel/Generator/Builder/Om/AbstractOMBuilder.php

http://github.com/propelorm/Propel2
PHP | 1172 lines | 661 code | 126 blank | 385 comment | 84 complexity | c947c2c20053b1941ebee0bc6a1096ab MD5 | raw file
  1. <?php
  2. /**
  3. * MIT License. This file is part of the Propel package.
  4. * For the full copyright and license information, please view the LICENSE
  5. * file that was distributed with this source code.
  6. */
  7. namespace Propel\Generator\Builder\Om;
  8. use InvalidArgumentException as CoreInvalidArgumentException;
  9. use Propel\Generator\Builder\DataModelBuilder;
  10. use Propel\Generator\Builder\Util\PropelTemplate;
  11. use Propel\Generator\Exception\InvalidArgumentException;
  12. use Propel\Generator\Exception\LogicException;
  13. use Propel\Generator\Exception\RuntimeException;
  14. use Propel\Generator\Model\Column;
  15. use Propel\Generator\Model\CrossForeignKeys;
  16. use Propel\Generator\Model\ForeignKey;
  17. use Propel\Generator\Model\Table;
  18. /**
  19. * Baseclass for OM-building classes.
  20. *
  21. * OM-building classes are those that build a PHP (or other) class to service
  22. * a single table. This includes Entity classes, Map classes,
  23. * Node classes, Nested Set classes, etc.
  24. *
  25. * @author Hans Lellelid <hans@xmpl.org>
  26. */
  27. abstract class AbstractOMBuilder extends DataModelBuilder
  28. {
  29. /**
  30. * Declared fully qualified classnames, to build the 'namespace' statements
  31. * according to this table's namespace.
  32. *
  33. * @var array
  34. */
  35. protected $declaredClasses = [];
  36. /**
  37. * Mapping between fully qualified classnames and their short classname or alias
  38. *
  39. * @var array
  40. */
  41. protected $declaredShortClassesOrAlias = [];
  42. /**
  43. * List of classes that can be use without alias when model don't have namespace
  44. *
  45. * @var array
  46. */
  47. protected $whiteListOfDeclaredClasses = ['PDO', 'Exception', 'DateTime'];
  48. /**
  49. * Builds the PHP source for current class and returns it as a string.
  50. *
  51. * This is the main entry point and defines a basic structure that classes should follow.
  52. * In most cases this method will not need to be overridden by subclasses. This method
  53. * does assume that the output language is PHP code, so it will need to be overridden if
  54. * this is not the case.
  55. *
  56. * @return string The resulting PHP sourcecode.
  57. */
  58. public function build()
  59. {
  60. $this->validateModel();
  61. $this->declareClass($this->getFullyQualifiedClassName());
  62. $script = '';
  63. $this->addClassOpen($script);
  64. $this->addClassBody($script);
  65. $this->addClassClose($script);
  66. $ignoredNamespace = ltrim($this->getNamespace(), '\\');
  67. if ($useStatements = $this->getUseStatements($ignoredNamespace ?: 'namespace')) {
  68. $script = $useStatements . $script;
  69. }
  70. if ($namespaceStatement = $this->getNamespaceStatement()) {
  71. $script = $namespaceStatement . $script;
  72. }
  73. $script = "<?php
  74. " . $script;
  75. return $this->clean($script);
  76. }
  77. /**
  78. * Validates the current table to make sure that it won't
  79. * result in generated code that will not parse.
  80. *
  81. * This method may emit warnings for code which may cause problems
  82. * and will throw exceptions for errors that will definitely cause
  83. * problems.
  84. *
  85. * @return void
  86. */
  87. protected function validateModel()
  88. {
  89. // Validation is currently only implemented in the subclasses.
  90. }
  91. /**
  92. * Creates a $obj = new Book(); code snippet. Can be used by frameworks, for instance, to
  93. * extend this behavior, e.g. initialize the object after creating the instance or so.
  94. *
  95. * @param string $objName
  96. * @param string $clsName
  97. *
  98. * @return string Some code
  99. */
  100. public function buildObjectInstanceCreationCode($objName, $clsName)
  101. {
  102. return "$objName = new $clsName();";
  103. }
  104. /**
  105. * Returns the qualified (prefixed) classname that is being built by the current class.
  106. * This method must be implemented by child classes.
  107. *
  108. * @return string
  109. */
  110. abstract public function getUnprefixedClassName();
  111. /**
  112. * Returns the unqualified classname (e.g. Book)
  113. *
  114. * @return string
  115. */
  116. public function getUnqualifiedClassName()
  117. {
  118. return $this->getUnprefixedClassName();
  119. }
  120. /**
  121. * Returns the qualified classname (e.g. Model\Book)
  122. *
  123. * @return string
  124. */
  125. public function getQualifiedClassName()
  126. {
  127. if ($namespace = $this->getNamespace()) {
  128. return $namespace . '\\' . $this->getUnqualifiedClassName();
  129. }
  130. return $this->getUnqualifiedClassName();
  131. }
  132. /**
  133. * Returns the fully qualified classname (e.g. \Model\Book)
  134. *
  135. * @return string
  136. */
  137. public function getFullyQualifiedClassName()
  138. {
  139. return '\\' . $this->getQualifiedClassName();
  140. }
  141. /**
  142. * Returns FQCN alias of getFullyQualifiedClassName
  143. *
  144. * @return string
  145. */
  146. public function getClassName()
  147. {
  148. return $this->getFullyQualifiedClassName();
  149. }
  150. /**
  151. * Gets the dot-path representation of current class being built.
  152. *
  153. * @return string
  154. */
  155. public function getClasspath()
  156. {
  157. if ($this->getPackage()) {
  158. return $this->getPackage() . '.' . $this->getUnqualifiedClassName();
  159. }
  160. return $this->getUnqualifiedClassName();
  161. }
  162. /**
  163. * Gets the full path to the file for the current class.
  164. *
  165. * @return string
  166. */
  167. public function getClassFilePath()
  168. {
  169. return ClassTools::createFilePath($this->getPackagePath(), $this->getUnqualifiedClassName());
  170. }
  171. /**
  172. * Gets package name for this table.
  173. * This is overridden by child classes that have different packages.
  174. *
  175. * @return string
  176. */
  177. public function getPackage()
  178. {
  179. $pkg = ($this->getTable()->getPackage() ? $this->getTable()->getPackage() : $this->getDatabase()->getPackage());
  180. if (!$pkg) {
  181. $pkg = $this->getBuildProperty('generator.targetPackage');
  182. }
  183. return $pkg;
  184. }
  185. /**
  186. * Returns filesystem path for current package.
  187. *
  188. * @return string
  189. */
  190. public function getPackagePath()
  191. {
  192. $pkg = $this->getPackage();
  193. if (strpos($pkg, '/') !== false) {
  194. $pkg = preg_replace('#\.(map|om)$#', '/\1', $pkg);
  195. $pkg = preg_replace('#\.(Map|Om)$#', '/\1', $pkg);
  196. return $pkg;
  197. }
  198. $path = $pkg;
  199. $path = str_replace('...', '$$/', $path);
  200. $path = strtr(ltrim($path, '.'), '.', '/');
  201. $path = str_replace('$$/', '../', $path);
  202. return $path;
  203. }
  204. /**
  205. * Returns the user-defined namespace for this table,
  206. * or the database namespace otherwise.
  207. *
  208. * @return string
  209. */
  210. public function getNamespace()
  211. {
  212. return $this->getTable()->getNamespace();
  213. }
  214. /**
  215. * This declares the class use and returns the correct name to use (short classname, Alias, or FQCN)
  216. *
  217. * @param \Propel\Generator\Builder\Om\AbstractOMBuilder $builder
  218. * @param bool $fqcn true to return the $fqcn classname
  219. *
  220. * @return string ClassName, Alias or FQCN
  221. */
  222. public function getClassNameFromBuilder($builder, $fqcn = false)
  223. {
  224. if ($fqcn) {
  225. return $builder->getFullyQualifiedClassName();
  226. }
  227. $namespace = $builder->getNamespace();
  228. $class = $builder->getUnqualifiedClassName();
  229. if (
  230. isset($this->declaredClasses[$namespace])
  231. && isset($this->declaredClasses[$namespace][$class])
  232. ) {
  233. return $this->declaredClasses[$namespace][$class];
  234. }
  235. return $this->declareClassNamespace($class, $namespace, true);
  236. }
  237. /**
  238. * This declares the class use and returns the correct name to use
  239. *
  240. * @param \Propel\Generator\Model\Table $table
  241. * @param bool $fqcn
  242. *
  243. * @return string
  244. */
  245. public function getClassNameFromTable(Table $table, $fqcn = false)
  246. {
  247. $namespace = $table->getNamespace();
  248. $class = $table->getPhpName();
  249. return $this->declareClassNamespace($class, $namespace, true);
  250. }
  251. /**
  252. * Declare a class to be use and return it's name or it's alias
  253. *
  254. * @param string $class the class name
  255. * @param string $namespace the namespace
  256. * @param string|bool|null $alias the alias wanted, if set to True, it automatically adds an alias when needed
  257. *
  258. * @throws \Propel\Generator\Exception\LogicException
  259. *
  260. * @return string the class name or it's alias
  261. */
  262. public function declareClassNamespace($class, $namespace = '', $alias = false)
  263. {
  264. $namespace = trim($namespace, '\\');
  265. // check if the class is already declared
  266. if (
  267. isset($this->declaredClasses[$namespace])
  268. && isset($this->declaredClasses[$namespace][$class])
  269. ) {
  270. return $this->declaredClasses[$namespace][$class];
  271. }
  272. $forcedAlias = $this->needAliasForClassName($class, $namespace);
  273. if ($alias === false || $alias === true || $alias === null) {
  274. $aliasWanted = $class;
  275. $alias = $alias || $forcedAlias;
  276. } else {
  277. $aliasWanted = $alias;
  278. $forcedAlias = false;
  279. }
  280. if (!$forcedAlias && !isset($this->declaredShortClassesOrAlias[$aliasWanted])) {
  281. if (!isset($this->declaredClasses[$namespace])) {
  282. $this->declaredClasses[$namespace] = [];
  283. }
  284. $this->declaredClasses[$namespace][$class] = $aliasWanted;
  285. $this->declaredShortClassesOrAlias[$aliasWanted] = $namespace . '\\' . $class;
  286. return $aliasWanted;
  287. }
  288. // we have a duplicate class and asked for an automatic Alias
  289. if ($alias !== false) {
  290. if (substr($namespace, -5) === '\\Base' || $namespace === 'Base') {
  291. return $this->declareClassNamespace($class, $namespace, 'Base' . $class);
  292. }
  293. if (substr($alias, 0, 5) === 'Child') {
  294. //we already requested Child.$class and its in use too,
  295. //so use the fqcn
  296. return ($namespace ? '\\' . $namespace : '') . '\\' . $class;
  297. } else {
  298. $autoAliasName = 'Child' . $class;
  299. }
  300. return $this->declareClassNamespace($class, $namespace, $autoAliasName);
  301. }
  302. throw new LogicException(sprintf(
  303. 'The class %s duplicates the class %s and can\'t be used without alias',
  304. $namespace . '\\' . $class,
  305. $this->declaredShortClassesOrAlias[$aliasWanted]
  306. ));
  307. }
  308. /**
  309. * check if the current $class need an alias or if the class could be used with a shortname without conflict
  310. *
  311. * @param string $class
  312. * @param string $classNamespace
  313. *
  314. * @return bool
  315. */
  316. protected function needAliasForClassName($class, $classNamespace)
  317. {
  318. $builderNamespace = trim($this->getNamespace(), '\\');
  319. if ($classNamespace == $builderNamespace) {
  320. return false;
  321. }
  322. if (str_replace('\\Base', '', $classNamespace) == str_replace('\\Base', '', $builderNamespace)) {
  323. return true;
  324. }
  325. if (empty($classNamespace) && $builderNamespace === 'Base') {
  326. if (str_replace(['Query'], '', $class) == str_replace(['Query'], '', $this->getUnqualifiedClassName())) {
  327. return true;
  328. }
  329. if ((strpos($class, 'Query') !== false)) {
  330. return true;
  331. }
  332. // force alias for model without namespace
  333. if (!in_array($class, $this->whiteListOfDeclaredClasses, true)) {
  334. return true;
  335. }
  336. }
  337. if ($classNamespace === 'Base' && $builderNamespace === '') {
  338. // force alias for model without namespace
  339. if (!in_array($class, $this->whiteListOfDeclaredClasses, true)) {
  340. return true;
  341. }
  342. }
  343. return false;
  344. }
  345. /**
  346. * Declare a use statement for a $class with a $namespace and an $aliasPrefix
  347. * This return the short ClassName or an alias
  348. *
  349. * @param string $class the class
  350. * @param string $namespace the namespace
  351. * @param mixed $aliasPrefix optionally an alias or True to force an automatic alias prefix (Base or Child)
  352. *
  353. * @return string the short ClassName or an alias
  354. */
  355. public function declareClassNamespacePrefix($class, $namespace = '', $aliasPrefix = false)
  356. {
  357. if ($aliasPrefix !== false && $aliasPrefix !== true) {
  358. $alias = $aliasPrefix . $class;
  359. } else {
  360. $alias = $aliasPrefix;
  361. }
  362. return $this->declareClassNamespace($class, $namespace, $alias);
  363. }
  364. /**
  365. * Declare a Fully qualified classname with an $aliasPrefix
  366. * This return the short ClassName to use or an alias
  367. *
  368. * @param string $fullyQualifiedClassName the fully qualified classname
  369. * @param mixed $aliasPrefix optionally an alias or True to force an automatic alias prefix (Base or Child)
  370. *
  371. * @return string the short ClassName or an alias
  372. */
  373. public function declareClass($fullyQualifiedClassName, $aliasPrefix = false)
  374. {
  375. $fullyQualifiedClassName = trim($fullyQualifiedClassName, '\\');
  376. if (($pos = strrpos($fullyQualifiedClassName, '\\')) !== false) {
  377. return $this->declareClassNamespacePrefix(substr($fullyQualifiedClassName, $pos + 1), substr($fullyQualifiedClassName, 0, $pos), $aliasPrefix);
  378. }
  379. // root namespace
  380. return $this->declareClassNamespacePrefix($fullyQualifiedClassName, '', $aliasPrefix);
  381. }
  382. /**
  383. * @param self $builder
  384. * @param bool|string $aliasPrefix the prefix for the Alias or True for auto generation of the Alias
  385. *
  386. * @return string
  387. */
  388. public function declareClassFromBuilder(self $builder, $aliasPrefix = false)
  389. {
  390. return $this->declareClassNamespacePrefix($builder->getUnqualifiedClassName(), $builder->getNamespace(), $aliasPrefix);
  391. }
  392. /**
  393. * @return void
  394. */
  395. public function declareClasses()
  396. {
  397. $args = func_get_args();
  398. foreach ($args as $class) {
  399. $this->declareClass($class);
  400. }
  401. }
  402. /**
  403. * Get the list of declared classes for a given $namespace or all declared classes
  404. *
  405. * @param string|null $namespace the namespace or null
  406. *
  407. * @return array list of declared classes
  408. */
  409. public function getDeclaredClasses($namespace = null)
  410. {
  411. if ($namespace !== null && isset($this->declaredClasses[$namespace])) {
  412. return $this->declaredClasses[$namespace];
  413. }
  414. return $this->declaredClasses;
  415. }
  416. /**
  417. * return the string for the class namespace
  418. *
  419. * @return string|null
  420. */
  421. public function getNamespaceStatement()
  422. {
  423. $namespace = $this->getNamespace();
  424. if (!empty($namespace)) {
  425. return sprintf("namespace %s;
  426. ", $namespace);
  427. }
  428. return null;
  429. }
  430. /**
  431. * Return all the use statement of the class
  432. *
  433. * @param string|null $ignoredNamespace the ignored namespace
  434. *
  435. * @return string
  436. */
  437. public function getUseStatements($ignoredNamespace = null)
  438. {
  439. $script = '';
  440. $declaredClasses = $this->declaredClasses;
  441. unset($declaredClasses[$ignoredNamespace]);
  442. ksort($declaredClasses);
  443. foreach ($declaredClasses as $namespace => $classes) {
  444. asort($classes);
  445. foreach ($classes as $class => $alias) {
  446. // Don't use our own class
  447. if ($class == $this->getUnqualifiedClassName() && $namespace == $this->getNamespace()) {
  448. continue;
  449. }
  450. if ($class == $alias) {
  451. $script .= sprintf("use %s\\%s;
  452. ", $namespace, $class);
  453. } else {
  454. $script .= sprintf("use %s\\%s as %s;
  455. ", $namespace, $class, $alias);
  456. }
  457. }
  458. }
  459. return $script;
  460. }
  461. /**
  462. * Shortcut method to return the [stub] query classname for current table.
  463. * This is the classname that is used whenever object or tablemap classes want
  464. * to invoke methods of the query classes.
  465. *
  466. * @param bool $fqcn
  467. *
  468. * @return string (e.g. 'Myquery')
  469. */
  470. public function getQueryClassName($fqcn = false)
  471. {
  472. return $this->getClassNameFromBuilder($this->getStubQueryBuilder(), $fqcn);
  473. }
  474. /**
  475. * Returns the object classname for current table.
  476. * This is the classname that is used whenever object or tablemap classes want
  477. * to invoke methods of the object classes.
  478. *
  479. * @param bool $fqcn
  480. *
  481. * @return string (e.g. 'MyTable' or 'ChildMyTable')
  482. */
  483. public function getObjectClassName($fqcn = false)
  484. {
  485. return $this->getClassNameFromBuilder($this->getStubObjectBuilder(), $fqcn);
  486. }
  487. /**
  488. * Returns always the final unqualified object class name. This is only useful for documentation/phpdoc,
  489. * not in the actual code.
  490. *
  491. * @return string
  492. */
  493. public function getObjectName()
  494. {
  495. return $this->getStubObjectBuilder()->getUnqualifiedClassName();
  496. }
  497. /**
  498. * Returns the tableMap classname for current table.
  499. * This is the classname that is used whenever object or tablemap classes want
  500. * to invoke methods of the object classes.
  501. *
  502. * @param bool $fqcn
  503. *
  504. * @return string (e.g. 'My')
  505. */
  506. public function getTableMapClassName($fqcn = false)
  507. {
  508. return $this->getClassNameFromBuilder($this->getTableMapBuilder(), $fqcn);
  509. }
  510. /**
  511. * Get the column constant name (e.g. TableMapName::COLUMN_NAME).
  512. *
  513. * @param \Propel\Generator\Model\Column|null $col The column we need a name for.
  514. * @param string|null $classname The TableMap classname to use.
  515. *
  516. * @throws \Propel\Generator\Exception\InvalidArgumentException
  517. *
  518. * @return string If $classname is provided, then will return $classname::COLUMN_NAME; if not, then the TableMapName is looked up for current table to yield $currTableTableMap::COLUMN_NAME.
  519. */
  520. public function getColumnConstant($col, $classname = null)
  521. {
  522. if ($col === null) {
  523. throw new InvalidArgumentException('No columns were specified.');
  524. }
  525. if ($classname === null) {
  526. return $this->getBuildProperty('generator.objectModel.classPrefix') . $col->getFQConstantName();
  527. }
  528. // was it overridden in schema.xml ?
  529. if ($col->getTableMapName()) {
  530. $const = strtoupper($col->getTableMapName());
  531. } else {
  532. $const = strtoupper($col->getName());
  533. }
  534. return $classname . '::' . Column::CONSTANT_PREFIX . $const;
  535. }
  536. /**
  537. * Convenience method to get the default Join Type for a relation.
  538. * If the key is required, an INNER JOIN will be returned, else a LEFT JOIN will be suggested,
  539. * unless the schema is provided with the DefaultJoin attribute, which overrules the default Join Type
  540. *
  541. * @param \Propel\Generator\Model\ForeignKey $fk
  542. *
  543. * @return string
  544. */
  545. protected function getJoinType(ForeignKey $fk)
  546. {
  547. if ($defaultJoin = $fk->getDefaultJoin()) {
  548. return "'" . $defaultJoin . "'";
  549. }
  550. if ($fk->isLocalColumnsRequired()) {
  551. return 'Criteria::INNER_JOIN';
  552. }
  553. return 'Criteria::LEFT_JOIN';
  554. }
  555. /**
  556. * Gets the PHP method name affix to be used for fkeys for the current table (not referrers to this table).
  557. *
  558. * The difference between this method and the getRefFKPhpNameAffix() method is that in this method the
  559. * classname in the affix is the foreign table classname.
  560. *
  561. * @param \Propel\Generator\Model\ForeignKey $fk The local FK that we need a name for.
  562. * @param bool $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
  563. *
  564. * @return string
  565. */
  566. public function getFKPhpNameAffix(ForeignKey $fk, $plural = false)
  567. {
  568. if ($fk->getPhpName()) {
  569. if ($plural) {
  570. return $this->getPluralizer()->getPluralForm($fk->getPhpName());
  571. }
  572. return $fk->getPhpName();
  573. }
  574. $className = $fk->getForeignTable()->getPhpName();
  575. if ($plural) {
  576. $className = $this->getPluralizer()->getPluralForm($className);
  577. }
  578. return $className . $this->getRelatedBySuffix($fk);
  579. }
  580. /**
  581. * @param \Propel\Generator\Model\CrossForeignKeys $crossFKs
  582. * @param bool $plural
  583. *
  584. * @return string
  585. */
  586. protected function getCrossFKsPhpNameAffix(CrossForeignKeys $crossFKs, $plural = true)
  587. {
  588. $names = [];
  589. if ($plural) {
  590. if ($crossFKs->getUnclassifiedPrimaryKeys()) {
  591. //we have a non fk as pk as well, so we need to make pluralisation on our own and can't
  592. //rely on getFKPhpNameAffix's pluralisation
  593. foreach ($crossFKs->getCrossForeignKeys() as $fk) {
  594. $names[] = $this->getFKPhpNameAffix($fk, false);
  595. }
  596. } else {
  597. //we have only fks, so give us names with plural and return those
  598. $lastIdx = count($crossFKs->getCrossForeignKeys()) - 1;
  599. foreach ($crossFKs->getCrossForeignKeys() as $idx => $fk) {
  600. $needPlural = $idx === $lastIdx; //only last fk should be plural
  601. $names[] = $this->getFKPhpNameAffix($fk, $needPlural);
  602. }
  603. return implode('', $names);
  604. }
  605. } else {
  606. // no plural, so $plural=false
  607. foreach ($crossFKs->getCrossForeignKeys() as $fk) {
  608. $names[] = $this->getFKPhpNameAffix($fk, false);
  609. }
  610. }
  611. foreach ($crossFKs->getUnclassifiedPrimaryKeys() as $pk) {
  612. $names[] = $pk->getPhpName();
  613. }
  614. $name = implode('', $names);
  615. return ($plural === true ? $this->getPluralizer()->getPluralForm($name) : $name);
  616. }
  617. /**
  618. * @param \Propel\Generator\Model\CrossForeignKeys $crossFKs
  619. * @param \Propel\Generator\Model\ForeignKey $excludeFK
  620. *
  621. * @return string
  622. */
  623. protected function getCrossRefFKGetterName(CrossForeignKeys $crossFKs, ForeignKey $excludeFK)
  624. {
  625. $names = [];
  626. $fks = $crossFKs->getCrossForeignKeys();
  627. foreach ($crossFKs->getMiddleTable()->getForeignKeys() as $fk) {
  628. if ($fk !== $excludeFK && ($fk === $crossFKs->getIncomingForeignKey() || in_array($fk, $fks))) {
  629. $names[] = $this->getFKPhpNameAffix($fk, false);
  630. }
  631. }
  632. foreach ($crossFKs->getUnclassifiedPrimaryKeys() as $pk) {
  633. $names[] = $pk->getPhpName();
  634. }
  635. $name = implode('', $names);
  636. return $this->getPluralizer()->getPluralForm($name);
  637. }
  638. /**
  639. * @param \Propel\Generator\Model\CrossForeignKeys $crossFKs
  640. *
  641. * @return array
  642. */
  643. protected function getCrossFKInformation(CrossForeignKeys $crossFKs)
  644. {
  645. $names = [];
  646. $signatures = [];
  647. $shortSignature = [];
  648. $phpDoc = [];
  649. foreach ($crossFKs->getCrossForeignKeys() as $fk) {
  650. $crossObjectName = '$' . lcfirst($this->getFKPhpNameAffix($fk));
  651. $crossObjectClassName = $this->getNewObjectBuilder($fk->getForeignTable())->getObjectClassName();
  652. $names[] = $crossObjectClassName;
  653. $signatures[] = "$crossObjectClassName $crossObjectName" . ($fk->isAtLeastOneLocalColumnRequired() ? '' : ' = null');
  654. $shortSignature[] = $crossObjectName;
  655. $phpDoc[] = "
  656. * @param $crossObjectClassName $crossObjectName The object to relate";
  657. }
  658. $names = implode(', ', $names) . (1 < count($names) ? ' combination' : '');
  659. $phpDoc = implode('', $phpDoc);
  660. $signatures = implode(', ', $signatures);
  661. $shortSignature = implode(', ', $shortSignature);
  662. return [
  663. $names,
  664. $phpDoc,
  665. $signatures,
  666. $shortSignature,
  667. ];
  668. }
  669. /**
  670. * @param \Propel\Generator\Model\CrossForeignKeys $crossFKs
  671. * @param array|\Propel\Generator\Model\ForeignKey|null $crossFK will be the first variable defined
  672. *
  673. * @return array
  674. */
  675. protected function getCrossFKAddMethodInformation(CrossForeignKeys $crossFKs, $crossFK = null)
  676. {
  677. if ($crossFK instanceof ForeignKey) {
  678. $crossObjectName = '$' . lcfirst($this->getFKPhpNameAffix($crossFK));
  679. $crossObjectClassName = $this->getClassNameFromTable($crossFK->getForeignTable());
  680. $signature[] = "$crossObjectClassName $crossObjectName" . ($crossFK->isAtLeastOneLocalColumnRequired() ? '' : ' = null');
  681. $shortSignature[] = $crossObjectName;
  682. $normalizedShortSignature[] = $crossObjectName;
  683. $phpDoc[] = "
  684. * @param $crossObjectClassName $crossObjectName";
  685. }
  686. $this->extractCrossInformation($crossFKs, $crossFK, $signature, $shortSignature, $normalizedShortSignature, $phpDoc);
  687. $signature = implode(', ', $signature);
  688. $shortSignature = implode(', ', $shortSignature);
  689. $normalizedShortSignature = implode(', ', $normalizedShortSignature);
  690. $phpDoc = implode(', ', $phpDoc);
  691. return [$signature, $shortSignature, $normalizedShortSignature, $phpDoc];
  692. }
  693. /**
  694. * Extracts some useful information from a CrossForeignKeys object.
  695. *
  696. * @param \Propel\Generator\Model\CrossForeignKeys $crossFKs
  697. * @param array|\Propel\Generator\Model\ForeignKey $crossFKToIgnore
  698. * @param array $signature
  699. * @param array $shortSignature
  700. * @param array $normalizedShortSignature
  701. * @param array $phpDoc
  702. *
  703. * @return void
  704. */
  705. protected function extractCrossInformation(
  706. CrossForeignKeys $crossFKs,
  707. $crossFKToIgnore,
  708. &$signature,
  709. &$shortSignature,
  710. &$normalizedShortSignature,
  711. &$phpDoc
  712. ) {
  713. foreach ($crossFKs->getCrossForeignKeys() as $fk) {
  714. if (is_array($crossFKToIgnore) && in_array($fk, $crossFKToIgnore)) {
  715. continue;
  716. } elseif ($fk === $crossFKToIgnore) {
  717. continue;
  718. }
  719. $phpType = $typeHint = $this->getClassNameFromTable($fk->getForeignTable());
  720. $name = '$' . lcfirst($this->getFKPhpNameAffix($fk));
  721. $normalizedShortSignature[] = $name;
  722. $signature[] = ($typeHint ? "$typeHint " : '') . $name;
  723. $shortSignature[] = $name;
  724. $phpDoc[] = "
  725. * @param $phpType $name";
  726. }
  727. foreach ($crossFKs->getUnclassifiedPrimaryKeys() as $primaryKey) {
  728. //we need to add all those $primaryKey s as additional parameter as they are needed
  729. //to create the entry in the middle-table.
  730. $defaultValue = $primaryKey->getDefaultValueString();
  731. $phpType = $primaryKey->getPhpType();
  732. $typeHint = $primaryKey->isPhpArrayType() ? 'array' : '';
  733. $name = '$' . lcfirst($primaryKey->getPhpName());
  734. $normalizedShortSignature[] = $name;
  735. $signature[] = ($typeHint ? "$typeHint " : '') . $name . ($defaultValue !== 'null' ? " = $defaultValue" : '');
  736. $shortSignature[] = $name;
  737. $phpDoc[] = "
  738. * @param $phpType $name";
  739. }
  740. }
  741. /**
  742. * @param \Propel\Generator\Model\CrossForeignKeys $crossFKs
  743. *
  744. * @return string
  745. */
  746. protected function getCrossFKsVarName(CrossForeignKeys $crossFKs)
  747. {
  748. return 'coll' . $this->getCrossFKsPhpNameAffix($crossFKs);
  749. }
  750. /**
  751. * @param \Propel\Generator\Model\ForeignKey $crossFK
  752. *
  753. * @return string
  754. */
  755. protected function getCrossFKVarName(ForeignKey $crossFK)
  756. {
  757. return 'coll' . $this->getFKPhpNameAffix($crossFK, true);
  758. }
  759. /**
  760. * Gets the "RelatedBy*" suffix (if needed) that is attached to method and variable names.
  761. *
  762. * The related by suffix is based on the local columns of the foreign key. If there is more than
  763. * one column in a table that points to the same foreign table, then a 'RelatedByLocalColName' suffix
  764. * will be appended.
  765. *
  766. * @param \Propel\Generator\Model\ForeignKey $fk
  767. *
  768. * @throws \Propel\Generator\Exception\RuntimeException
  769. *
  770. * @return string
  771. */
  772. protected static function getRelatedBySuffix(ForeignKey $fk)
  773. {
  774. $relCol = '';
  775. foreach ($fk->getMapping() as $mapping) {
  776. [$localColumn, $foreignValueOrColumn] = $mapping;
  777. $localColumnName = $localColumn->getPhpName();
  778. $localTable = $fk->getTable();
  779. if (!$localColumn) {
  780. throw new RuntimeException(sprintf('Could not fetch column: %s in table %s.', $localColumnName, $localTable->getName()));
  781. }
  782. if (
  783. count($localTable->getForeignKeysReferencingTable($fk->getForeignTableName())) > 1
  784. || count($fk->getForeignTable()->getForeignKeysReferencingTable($fk->getTableName())) > 0
  785. || $fk->getForeignTableName() == $fk->getTableName()
  786. ) {
  787. // self referential foreign key, or several foreign keys to the same table, or cross-reference fkey
  788. $relCol .= $localColumn->getPhpName();
  789. }
  790. }
  791. if (!empty($relCol)) {
  792. $relCol = 'RelatedBy' . $relCol;
  793. }
  794. return $relCol;
  795. }
  796. /**
  797. * Gets the PHP method name affix to be used for referencing foreign key methods and variable names (e.g. set????(), $coll???).
  798. *
  799. * The difference between this method and the getFKPhpNameAffix() method is that in this method the
  800. * classname in the affix is the classname of the local fkey table.
  801. *
  802. * @param \Propel\Generator\Model\ForeignKey $fk The referrer FK that we need a name for.
  803. * @param bool $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
  804. *
  805. * @return string
  806. */
  807. public function getRefFKPhpNameAffix(ForeignKey $fk, $plural = false)
  808. {
  809. $pluralizer = $this->getPluralizer();
  810. if ($fk->getRefPhpName()) {
  811. return $plural ? $pluralizer->getPluralForm($fk->getRefPhpName()) : $fk->getRefPhpName();
  812. }
  813. $className = $fk->getTable()->getPhpName();
  814. if ($plural) {
  815. $className = $pluralizer->getPluralForm($className);
  816. }
  817. return $className . $this->getRefRelatedBySuffix($fk);
  818. }
  819. /**
  820. * @param \Propel\Generator\Model\ForeignKey $fk
  821. *
  822. * @throws \Propel\Generator\Exception\RuntimeException
  823. *
  824. * @return string
  825. */
  826. protected static function getRefRelatedBySuffix(ForeignKey $fk)
  827. {
  828. $relCol = '';
  829. foreach ($fk->getMapping() as $mapping) {
  830. [$localColumn, $foreignValueOrColumn] = $mapping;
  831. $localColumnName = $localColumn->getPhpName();
  832. $localTable = $fk->getTable();
  833. if (!$localColumn) {
  834. throw new RuntimeException(sprintf('Could not fetch column: %s in table %s.', $localColumnName, $localTable->getName()));
  835. }
  836. $foreignKeysToForeignTable = $localTable->getForeignKeysReferencingTable($fk->getForeignTableName());
  837. if ($foreignValueOrColumn instanceof Column && $fk->getForeignTableName() == $fk->getTableName()) {
  838. $foreignColumnName = $foreignValueOrColumn->getPhpName();
  839. // self referential foreign key
  840. $relCol .= $foreignColumnName;
  841. if (count($foreignKeysToForeignTable) > 1) {
  842. // several self-referential foreign keys
  843. $relCol .= array_search($fk, $foreignKeysToForeignTable);
  844. }
  845. } elseif (count($foreignKeysToForeignTable) > 1 || count($fk->getForeignTable()->getForeignKeysReferencingTable($fk->getTableName())) > 0) {
  846. // several foreign keys to the same table, or symmetrical foreign key in foreign table
  847. $relCol .= $localColumn->getPhpName();
  848. }
  849. }
  850. if (!empty($relCol)) {
  851. $relCol = 'RelatedBy' . $relCol;
  852. }
  853. return $relCol;
  854. }
  855. /**
  856. * Checks whether any registered behavior on that table has a modifier for a hook
  857. *
  858. * @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
  859. * @param string $modifier The name of the modifier object providing the method in the behavior
  860. *
  861. * @return bool
  862. */
  863. public function hasBehaviorModifier($hookName, $modifier)
  864. {
  865. $modifierGetter = 'get' . $modifier;
  866. foreach ($this->getTable()->getBehaviors() as $behavior) {
  867. if (method_exists($behavior->$modifierGetter(), $hookName)) {
  868. return true;
  869. }
  870. }
  871. return false;
  872. }
  873. /**
  874. * Checks whether any registered behavior on that table has a modifier for a hook
  875. *
  876. * @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
  877. * @param string $modifier The name of the modifier object providing the method in the behavior
  878. * @param string $script The script will be modified in this method.
  879. * @param string $tab
  880. *
  881. * @return void
  882. */
  883. public function applyBehaviorModifierBase($hookName, $modifier, &$script, $tab = ' ')
  884. {
  885. $modifierGetter = 'get' . $modifier;
  886. foreach ($this->getTable()->getBehaviors() as $behavior) {
  887. $modifier = $behavior->$modifierGetter();
  888. if (method_exists($modifier, $hookName)) {
  889. if (strpos($hookName, 'Filter') !== false) {
  890. // filter hook: the script string will be modified by the behavior
  891. $modifier->$hookName($script, $this);
  892. } else {
  893. // regular hook: the behavior returns a string to append to the script string
  894. $addedScript = $modifier->$hookName($this);
  895. if (!$addedScript) {
  896. continue;
  897. }
  898. $script .= "
  899. " . $tab . '// ' . $behavior->getId() . " behavior
  900. ";
  901. $script .= preg_replace('/^/m', $tab, $addedScript);
  902. }
  903. }
  904. }
  905. }
  906. /**
  907. * Checks whether any registered behavior content creator on that table exists a contentName
  908. *
  909. * @param string $contentName The name of the content as called from one of this class methods, e.g. "parentClassName"
  910. * @param string $modifier The name of the modifier object providing the method in the behavior
  911. *
  912. * @return string|null
  913. */
  914. public function getBehaviorContentBase($contentName, $modifier)
  915. {
  916. $modifierGetter = 'get' . $modifier;
  917. foreach ($this->getTable()->getBehaviors() as $behavior) {
  918. $modifier = $behavior->$modifierGetter();
  919. if (method_exists($modifier, $contentName)) {
  920. return $modifier->$contentName($this);
  921. }
  922. }
  923. return null;
  924. }
  925. /**
  926. * Use Propel simple templating system to render a PHP file using variables
  927. * passed as arguments. The template file name is relative to the behavior's
  928. * directory name.
  929. *
  930. * @param string $filename
  931. * @param array $vars
  932. * @param string $templateDir
  933. *
  934. * @throws \Propel\Generator\Exception\InvalidArgumentException
  935. *
  936. * @return string
  937. */
  938. public function renderTemplate($filename, $vars = [], $templateDir = '/templates/')
  939. {
  940. $filePath = __DIR__ . $templateDir . $filename;
  941. if (!file_exists($filePath)) {
  942. // try with '.php' at the end
  943. $filePath = $filePath . '.php';
  944. if (!file_exists($filePath)) {
  945. throw new CoreInvalidArgumentException(sprintf('Template "%s" not found in "%s" directory', $filename, __DIR__ . $templateDir));
  946. }
  947. }
  948. $template = new PropelTemplate();
  949. $template->setTemplateFile($filePath);
  950. $vars = array_merge($vars, ['behavior' => $this]);
  951. return $template->render($vars);
  952. }
  953. /**
  954. * @return string
  955. */
  956. public function getTableMapClass()
  957. {
  958. return $this->getStubObjectBuilder()->getUnqualifiedClassName() . 'TableMap';
  959. }
  960. /**
  961. * Most of the code comes from the PHP-CS-Fixer project
  962. *
  963. * @param string $content
  964. *
  965. * @return string
  966. */
  967. private function clean($content)
  968. {
  969. // line feed
  970. $content = str_replace("\r\n", "\n", $content);
  971. // trailing whitespaces
  972. $content = preg_replace('/[ \t]*$/m', '', $content);
  973. // indentation
  974. $content = preg_replace_callback('/^([ \t]+)/m', function ($matches) {
  975. return str_replace("\t", ' ', $matches[0]);
  976. }, $content);
  977. // Unused "use" statements
  978. preg_match_all('/^use (?P<class>[^\s;]+)(?:\s+as\s+(?P<alias>.*))?;/m', $content, $matches, PREG_SET_ORDER);
  979. foreach ($matches as $match) {
  980. if (isset($match['alias'])) {
  981. $short = $match['alias'];
  982. } else {
  983. $parts = explode('\\', $match['class']);
  984. $short = array_pop($parts);
  985. }
  986. preg_match_all('/\b' . $short . '\b/i', str_replace($match[0] . "\n", '', $content), $m);
  987. if (!count($m[0])) {
  988. $content = str_replace($match[0] . "\n", '', $content);
  989. }
  990. }
  991. // end of line
  992. if (strlen($content) && substr($content, -1) != "\n") {
  993. $content = $content . "\n";
  994. }
  995. return $content;
  996. }
  997. /**
  998. * Opens class.
  999. *
  1000. * @param string $script
  1001. *
  1002. * @return void
  1003. */
  1004. abstract protected function addClassOpen(&$script);
  1005. /**
  1006. * This method adds the contents of the generated class to the script.
  1007. *
  1008. * This method is abstract and should be overridden by the subclasses.
  1009. *
  1010. * Hint: Override this method in your subclass if you want to reorganize or
  1011. * drastically change the contents of the generated object class.
  1012. *
  1013. * @param string $script The script will be modified in this method.
  1014. *
  1015. * @return void
  1016. */
  1017. abstract protected function addClassBody(&$script);
  1018. /**
  1019. * Closes class.
  1020. *
  1021. * @param string $script
  1022. *
  1023. * @return void
  1024. */
  1025. abstract protected function addClassClose(&$script);
  1026. }