PageRenderTime 62ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/generator/lib/builder/om/OMBuilder.php

http://github.com/propelorm/Propel
PHP | 631 lines | 371 code | 60 blank | 200 comment | 52 complexity | ff159743d25634f6c70f7e0b6a155501 MD5 | raw file
  1. <?php
  2. /**
  3. * 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. * @license MIT License
  8. */
  9. require_once dirname(__FILE__) . '/../DataModelBuilder.php';
  10. /**
  11. * Baseclass for OM-building classes.
  12. *
  13. * OM-building classes are those that build a PHP (or other) class to service
  14. * a single table. This includes Peer classes, Entity classes, Map classes,
  15. * Node classes, Nested Set classes, etc.
  16. *
  17. * @author Hans Lellelid <hans@xmpl.org>
  18. * @package propel.generator.builder.om
  19. */
  20. abstract class OMBuilder extends DataModelBuilder
  21. {
  22. /**
  23. * Declared fully qualified classnames, to build the 'namespace' statements
  24. * according to this table's namespace.
  25. *
  26. * @var array
  27. */
  28. protected $declaredClasses = array();
  29. /**
  30. * Builds the PHP source for current class and returns it as a string.
  31. *
  32. * This is the main entry point and defines a basic structure that classes should follow.
  33. * In most cases this method will not need to be overridden by subclasses. This method
  34. * does assume that the output language is PHP code, so it will need to be overridden if
  35. * this is not the case.
  36. *
  37. * @return string The resulting PHP sourcecode.
  38. */
  39. public function build()
  40. {
  41. $this->validateModel();
  42. $script = '';
  43. if ($this->isAddIncludes()) {
  44. $this->addIncludes($script);
  45. }
  46. $this->addClassOpen($script);
  47. $this->addClassBody($script);
  48. $this->addClassClose($script);
  49. if ($useStatements = $this->getUseStatements($ignoredNamespace = $this->getNamespace())) {
  50. $script = $useStatements . $script;
  51. }
  52. if ($namespaceStatement = $this->getNamespaceStatement()) {
  53. $script = $namespaceStatement . $script;
  54. }
  55. $script = "<?php
  56. " . $script;
  57. return $this->clean($script);
  58. }
  59. /**
  60. * Validates the current table to make sure that it won't
  61. * result in generated code that will not parse.
  62. *
  63. * This method may emit warnings for code which may cause problems
  64. * and will throw exceptions for errors that will definitely cause
  65. * problems.
  66. */
  67. protected function validateModel()
  68. {
  69. // Validation is currently only implemented in the subclasses.
  70. }
  71. /**
  72. * Creates a $obj = new Book(); code snippet. Can be used by frameworks, for instance, to
  73. * extend this behavior, e.g. initialize the object after creating the instance or so.
  74. *
  75. * @return string Some code
  76. */
  77. public function buildObjectInstanceCreationCode($objName, $clsName)
  78. {
  79. return "$objName = new $clsName();";
  80. }
  81. /**
  82. * Returns the qualified (prefixed) classname that is being built by the current class.
  83. * This method must be implemented by child classes.
  84. *
  85. * @return string
  86. */
  87. abstract public function getUnprefixedClassname();
  88. /**
  89. * Returns the prefixed classname that is being built by the current class.
  90. *
  91. * @return string
  92. * @see DataModelBuilder#prefixClassname()
  93. */
  94. public function getClassname()
  95. {
  96. return $this->prefixClassname($this->getUnprefixedClassname());
  97. }
  98. /**
  99. * Returns the namespaced classname if there is a namespace, and the raw classname otherwise
  100. *
  101. * @return string
  102. */
  103. public function getFullyQualifiedClassname()
  104. {
  105. if ($namespace = $this->getNamespace()) {
  106. return $namespace . '\\' . $this->getClassname();
  107. } else {
  108. return $this->getClassname();
  109. }
  110. }
  111. /**
  112. * Gets the dot-path representation of current class being built.
  113. *
  114. * @return string
  115. */
  116. public function getClasspath()
  117. {
  118. if ($this->getPackage()) {
  119. $path = $this->getPackage() . '.' . $this->getClassname();
  120. } else {
  121. $path = $this->getClassname();
  122. }
  123. return $path;
  124. }
  125. /**
  126. * Gets the full path to the file for the current class.
  127. *
  128. * @return string
  129. */
  130. public function getClassFilePath()
  131. {
  132. return ClassTools::createFilePath($this->getPackagePath(), $this->getClassname());
  133. }
  134. /**
  135. * Gets package name for this table.
  136. * This is overridden by child classes that have different packages.
  137. *
  138. * @return string
  139. */
  140. public function getPackage()
  141. {
  142. $pkg = ($this->getTable()->getPackage() ? $this->getTable()->getPackage() : $this->getDatabase()->getPackage());
  143. if (!$pkg) {
  144. $pkg = $this->getBuildProperty('targetPackage');
  145. }
  146. return $pkg;
  147. }
  148. /**
  149. * Returns filesystem path for current package.
  150. *
  151. * @return string
  152. */
  153. public function getPackagePath()
  154. {
  155. $pkg = $this->getPackage();
  156. if (false !== strpos($pkg, '/')) {
  157. return preg_replace('#\.(map|om)$#', '/\1', $pkg);
  158. }
  159. if ('.' === substr($pkg, 0, 1)) {
  160. $pkg = substr($pkg, 1);
  161. }
  162. return strtr($pkg, '.', '/');
  163. }
  164. /**
  165. * Return the user-defined namespace for this table,
  166. * or the database namespace otherwise.
  167. *
  168. * @return string
  169. */
  170. public function getNamespace()
  171. {
  172. return $this->getTable()->getNamespace();
  173. }
  174. public function declareClassNamespace($class, $namespace = '')
  175. {
  176. if (isset($this->declaredClasses[$namespace]) && in_array($class, $this->declaredClasses[$namespace])) {
  177. return;
  178. }
  179. $this->declaredClasses[$namespace][] = $class;
  180. }
  181. public function declareClass($fullyQualifiedClassName)
  182. {
  183. $fullyQualifiedClassName = trim($fullyQualifiedClassName, '\\');
  184. if (($pos = strrpos($fullyQualifiedClassName, '\\')) !== false) {
  185. $this->declareClassNamespace(substr($fullyQualifiedClassName, $pos + 1), substr($fullyQualifiedClassName, 0, $pos));
  186. } else {
  187. // root namespace
  188. $this->declareClassNamespace($fullyQualifiedClassName);
  189. }
  190. }
  191. public function declareClassFromBuilder($builder)
  192. {
  193. $this->declareClassNamespace($builder->getClassname(), $builder->getNamespace());
  194. }
  195. public function declareClasses()
  196. {
  197. $args = func_get_args();
  198. foreach ($args as $class) {
  199. $this->declareClass($class);
  200. }
  201. }
  202. public function getDeclaredClasses($namespace = null)
  203. {
  204. if (null !== $namespace && isset($this->declaredClasses[$namespace])) {
  205. return $this->declaredClasses[$namespace];
  206. } else {
  207. return $this->declaredClasses;
  208. }
  209. }
  210. public function getNamespaceStatement()
  211. {
  212. $namespace = $this->getNamespace();
  213. if ($namespace != '') {
  214. return sprintf("namespace %s;
  215. ", $namespace);
  216. }
  217. }
  218. public function getUseStatements($ignoredNamespace = null)
  219. {
  220. $script = '';
  221. $declaredClasses = $this->declaredClasses;
  222. unset($declaredClasses[$ignoredNamespace]);
  223. ksort($declaredClasses);
  224. foreach ($declaredClasses as $namespace => $classes) {
  225. sort($classes);
  226. foreach ($classes as $class) {
  227. $script .= sprintf("use %s\\%s;
  228. ", $namespace, $class);
  229. }
  230. }
  231. return $script;
  232. }
  233. /**
  234. * Shortcut method to return the [stub] peer classname for current table.
  235. * This is the classname that is used whenever object or peer classes want
  236. * to invoke methods of the peer classes.
  237. *
  238. * @return string (e.g. 'MyPeer')
  239. * @see StubPeerBuilder::getClassname()
  240. */
  241. public function getPeerClassname()
  242. {
  243. return $this->getStubPeerBuilder()->getClassname();
  244. }
  245. /**
  246. * Shortcut method to return the [stub] query classname for current table.
  247. * This is the classname that is used whenever object or peer classes want
  248. * to invoke methods of the query classes.
  249. *
  250. * @return string (e.g. 'Myquery')
  251. * @see StubQueryBuilder::getClassname()
  252. */
  253. public function getQueryClassname()
  254. {
  255. return $this->getStubQueryBuilder()->getClassname();
  256. }
  257. /**
  258. * Returns the object classname for current table.
  259. * This is the classname that is used whenever object or peer classes want
  260. * to invoke methods of the object classes.
  261. *
  262. * @return string (e.g. 'My')
  263. * @see StubPeerBuilder::getClassname()
  264. */
  265. public function getObjectClassname()
  266. {
  267. return $this->getStubObjectBuilder()->getClassname();
  268. }
  269. /**
  270. * Get the column constant name (e.g. PeerName::COLUMN_NAME).
  271. *
  272. * @param Column $col The column we need a name for.
  273. * @param string $classname The Peer classname to use.
  274. *
  275. * @return string If $classname is provided, then will return $classname::COLUMN_NAME; if not, then the peername is looked up for current table to yield $currTablePeer::COLUMN_NAME.
  276. *
  277. * @throws Exception
  278. */
  279. public function getColumnConstant($col, $classname = null)
  280. {
  281. if ($col === null) {
  282. $e = new Exception("No col specified.");
  283. print $e;
  284. throw $e;
  285. }
  286. if ($classname === null) {
  287. return $this->getBuildProperty('classPrefix') . $col->getConstantName();
  288. }
  289. // was it overridden in schema.xml ?
  290. if ($col->getPeerName()) {
  291. $const = strtoupper($col->getPeerName());
  292. } else {
  293. $const = strtoupper($col->getName());
  294. }
  295. return $classname . '::' . $const;
  296. }
  297. /**
  298. * Gets the basePeer path if specified for table/db.
  299. * If not, will return 'propel.util.BasePeer'
  300. *
  301. * @return string
  302. */
  303. public function getBasePeer(Table $table)
  304. {
  305. $class = $table->getBasePeer();
  306. if ($class === null) {
  307. $class = "propel.util.BasePeer";
  308. }
  309. return $class;
  310. }
  311. /**
  312. * Convenience method to get the foreign Table object for an fkey.
  313. *
  314. * @deprecated use ForeignKey::getForeignTable() instead
  315. * @return Table
  316. */
  317. protected function getForeignTable(ForeignKey $fk)
  318. {
  319. return $this->getTable()->getDatabase()->getTable($fk->getForeignTableName());
  320. }
  321. /**
  322. * Convenience method to get the default Join Type for a relation.
  323. * If the key is required, an INNER JOIN will be returned, else a LEFT JOIN will be suggested,
  324. * unless the schema is provided with the DefaultJoin attribute, which overrules the default Join Type
  325. *
  326. * @param ForeignKey $fk
  327. *
  328. * @return string
  329. */
  330. protected function getJoinType(ForeignKey $fk)
  331. {
  332. if ($defaultJoin = $fk->getDefaultJoin()) {
  333. return "'" . $defaultJoin . "'";
  334. }
  335. if ($fk->isLocalColumnsRequired()) {
  336. return 'Criteria::INNER_JOIN';
  337. }
  338. return 'Criteria::LEFT_JOIN';
  339. }
  340. /**
  341. * Gets the PHP method name affix to be used for fkeys for the current table (not referrers to this table).
  342. *
  343. * The difference between this method and the getRefFKPhpNameAffix() method is that in this method the
  344. * classname in the affix is the foreign table classname.
  345. *
  346. * @param ForeignKey $fk The local FK that we need a name for.
  347. * @param boolean $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
  348. *
  349. * @return string
  350. */
  351. public function getFKPhpNameAffix(ForeignKey $fk, $plural = false)
  352. {
  353. if ($fk->getPhpName()) {
  354. if ($plural) {
  355. return $this->getPluralizer()->getPluralForm($fk->getPhpName());
  356. } else {
  357. return $fk->getPhpName();
  358. }
  359. } else {
  360. $className = $fk->getForeignTable()->getPhpName();
  361. if ($plural) {
  362. $className = $this->getPluralizer()->getPluralForm($className);
  363. }
  364. return $className . $this->getRelatedBySuffix($fk);
  365. }
  366. }
  367. /**
  368. * Gets the "RelatedBy*" suffix (if needed) that is attached to method and variable names.
  369. *
  370. * The related by suffix is based on the local columns of the foreign key. If there is more than
  371. * one column in a table that points to the same foreign table, then a 'RelatedByLocalColName' suffix
  372. * will be appended.
  373. *
  374. * @return string
  375. *
  376. * @throws Exception
  377. */
  378. protected static function getRelatedBySuffix(ForeignKey $fk)
  379. {
  380. $relCol = '';
  381. foreach ($fk->getLocalForeignMapping() as $localColumnName => $foreignColumnName) {
  382. $localTable = $fk->getTable();
  383. $localColumn = $localTable->getColumn($localColumnName);
  384. if (!$localColumn) {
  385. throw new Exception("Could not fetch column: $localColumnName in table " . $localTable->getName());
  386. }
  387. if (count($localTable->getForeignKeysReferencingTable($fk->getForeignTableName())) > 1
  388. || count($fk->getForeignTable()->getForeignKeysReferencingTable($fk->getTableName())) > 0
  389. || $fk->getForeignTableName() == $fk->getTableName()) {
  390. // self referential foreign key, or several foreign keys to the same table, or cross-reference fkey
  391. $relCol .= $localColumn->getPhpName();
  392. }
  393. }
  394. if ($relCol != '') {
  395. $relCol = 'RelatedBy' . $relCol;
  396. }
  397. return $relCol;
  398. }
  399. /**
  400. * Gets the PHP method name affix to be used for referencing foreign key methods and variable names (e.g. set????(), $coll???).
  401. *
  402. * The difference between this method and the getFKPhpNameAffix() method is that in this method the
  403. * classname in the affix is the classname of the local fkey table.
  404. *
  405. * @param ForeignKey $fk The referrer FK that we need a name for.
  406. * @param boolean $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
  407. *
  408. * @return string
  409. */
  410. public function getRefFKPhpNameAffix(ForeignKey $fk, $plural = false)
  411. {
  412. if ($fk->getRefPhpName()) {
  413. if ($plural) {
  414. return $this->getPluralizer()->getPluralForm($fk->getRefPhpName());
  415. } else {
  416. return $fk->getRefPhpName();
  417. }
  418. } else {
  419. $className = $fk->getTable()->getPhpName();
  420. if ($plural) {
  421. $className = $this->getPluralizer()->getPluralForm($className);
  422. }
  423. return $className . $this->getRefRelatedBySuffix($fk);
  424. }
  425. }
  426. protected static function getRefRelatedBySuffix(ForeignKey $fk)
  427. {
  428. $relCol = '';
  429. foreach ($fk->getLocalForeignMapping() as $localColumnName => $foreignColumnName) {
  430. $localTable = $fk->getTable();
  431. $localColumn = $localTable->getColumn($localColumnName);
  432. if (!$localColumn) {
  433. throw new Exception("Could not fetch column: $localColumnName in table " . $localTable->getName());
  434. }
  435. $foreignKeysToForeignTable = $localTable->getForeignKeysReferencingTable($fk->getForeignTableName());
  436. if ($fk->getForeignTableName() == $fk->getTableName()) {
  437. // self referential foreign key
  438. $relCol .= $fk->getForeignTable()->getColumn($foreignColumnName)->getPhpName();
  439. if (count($foreignKeysToForeignTable) > 1) {
  440. // several self-referential foreign keys
  441. $relCol .= array_search($fk, $foreignKeysToForeignTable);
  442. }
  443. } elseif (count($foreignKeysToForeignTable) > 1 || count($fk->getForeignTable()->getForeignKeysReferencingTable($fk->getTableName())) > 0) {
  444. // several foreign keys to the same table, or symmetrical foreign key in foreign table
  445. $relCol .= $localColumn->getPhpName();
  446. }
  447. }
  448. if ($relCol != '') {
  449. $relCol = 'RelatedBy' . $relCol;
  450. }
  451. return $relCol;
  452. }
  453. /**
  454. * Whether to add the include statements.
  455. * This is based on the build property propel.addIncludes
  456. */
  457. protected function isAddIncludes()
  458. {
  459. return $this->getBuildProperty('addIncludes');
  460. }
  461. /**
  462. * Checks whether any registered behavior on that table has a modifier for a hook
  463. *
  464. * @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
  465. * @param string $modifier The name of the modifier object providing the method in the behavior
  466. *
  467. * @return boolean
  468. */
  469. public function hasBehaviorModifier($hookName, $modifier)
  470. {
  471. $modifierGetter = 'get' . $modifier;
  472. foreach ($this->getTable()->getBehaviors() as $behavior) {
  473. if (method_exists($behavior->$modifierGetter(), $hookName)) {
  474. return true;
  475. }
  476. }
  477. return false;
  478. }
  479. /**
  480. * Checks whether any registered behavior on that table has a modifier for a hook
  481. *
  482. * @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
  483. * @param string $modifier The name of the modifier object providing the method in the behavior
  484. * @param string &$script The script will be modified in this method.
  485. * @param string $tab
  486. */
  487. public function applyBehaviorModifierBase($hookName, $modifier, &$script, $tab = " ")
  488. {
  489. $modifierGetter = 'get' . $modifier;
  490. foreach ($this->getTable()->getBehaviors() as $behavior) {
  491. $modifier = $behavior->$modifierGetter();
  492. if (method_exists($modifier, $hookName)) {
  493. if (strpos($hookName, 'Filter') !== false) {
  494. // filter hook: the script string will be modified by the behavior
  495. $modifier->$hookName($script, $this);
  496. } else {
  497. // regular hook: the behavior returns a string to append to the script string
  498. if (!$addedScript = $modifier->$hookName($this)) {
  499. continue;
  500. }
  501. $script .= "
  502. " . $tab . '// ' . $behavior->getName() . " behavior
  503. ";
  504. $script .= preg_replace('/^/m', $tab, $addedScript);
  505. }
  506. }
  507. }
  508. }
  509. /**
  510. * Checks whether any registered behavior content creator on that table exists a contentName
  511. *
  512. * @param string $contentName The name of the content as called from one of this class methods, e.g. "parentClassname"
  513. * @param string $modifier The name of the modifier object providing the method in the behavior
  514. */
  515. public function getBehaviorContentBase($contentName, $modifier)
  516. {
  517. $modifierGetter = 'get' . $modifier;
  518. foreach ($this->getTable()->getBehaviors() as $behavior) {
  519. $modifier = $behavior->$modifierGetter();
  520. if (method_exists($modifier, $contentName)) {
  521. return $modifier->$contentName($this);
  522. }
  523. }
  524. }
  525. /**
  526. * Most of the code comes from the PHP-CS-Fixer project
  527. */
  528. private function clean($content)
  529. {
  530. // trailing whitespaces
  531. $content = preg_replace('/[ \t]*$/m', '', $content);
  532. // indentation
  533. $content = preg_replace_callback('/^([ \t]+)/m', array($this, 'fixTrailingWhitespaces'), $content);
  534. // line feed
  535. $content = str_replace("\r\n", "\n", $content);
  536. // Unused "use" statements
  537. preg_match_all('/^use (?P<class>[^\s;]+)(?:\s+as\s+(?P<alias>.*))?;/m', $content, $matches, PREG_SET_ORDER);
  538. foreach ($matches as $match) {
  539. if (isset($match['alias'])) {
  540. $short = $match['alias'];
  541. } else {
  542. $parts = explode('\\', $match['class']);
  543. $short = array_pop($parts);
  544. }
  545. preg_match_all('/\b' . $short . '\b/i', str_replace($match[0] . "\n", '', $content), $m);
  546. if (!count($m[0])) {
  547. $content = str_replace($match[0] . "\n", '', $content);
  548. }
  549. }
  550. // end of line
  551. if (strlen($content) && "\n" != substr($content, -1)) {
  552. $content = $content . "\n";
  553. }
  554. return $content;
  555. }
  556. public function fixTrailingWhitespaces($matches)
  557. {
  558. return str_replace("\t", ' ', $matches[0]);
  559. }
  560. }