PageRenderTime 25ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/generator/lib/behavior/sluggable/SluggableBehavior.php

http://github.com/propelorm/Propel
PHP | 439 lines | 280 code | 50 blank | 109 comment | 44 complexity | b6bdd14f9b21c5c3e11e3132c08d1cd0 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. /**
  10. * Adds a slug column
  11. *
  12. * @author Francois Zaninotto
  13. * @author Massimiliano Arione
  14. * @version $Revision$
  15. * @package propel.generator.behavior.sluggable
  16. */
  17. class SluggableBehavior extends Behavior
  18. {
  19. // default parameters value
  20. protected $parameters = array(
  21. 'add_cleanup' => 'true',
  22. 'slug_column' => 'slug',
  23. 'slug_pattern' => '',
  24. 'replace_pattern' => '/\W+/', // Tip: use '/[^\\pL\\d]+/u' instead if you're in PHP5.3
  25. 'replacement' => '-',
  26. 'separator' => '-',
  27. 'permanent' => 'false',
  28. 'scope_column' => ''
  29. );
  30. /**
  31. * Add the slug_column to the current table
  32. */
  33. public function modifyTable()
  34. {
  35. if (!$this->getTable()->containsColumn($this->getParameter('slug_column'))) {
  36. $this->getTable()->addColumn(array(
  37. 'name' => $this->getParameter('slug_column'),
  38. 'type' => 'VARCHAR',
  39. 'size' => 255
  40. ));
  41. // add a unique to column
  42. $unique = new Unique($this->getColumnForParameter('slug_column'));
  43. $unique->setName($this->getTable()->getCommonName() . '_slug');
  44. $unique->addColumn($this->getTable()->getColumn($this->getParameter('slug_column')));
  45. if ($this->getParameter('scope_column')) {
  46. $unique->addColumn($this->getTable()->getColumn($this->getParameter('scope_column')));
  47. }
  48. $this->getTable()->addUnique($unique);
  49. }
  50. }
  51. /**
  52. * Get the getter of the column of the behavior
  53. *
  54. * @return string The related getter, e.g. 'getSlug'
  55. */
  56. protected function getColumnGetter()
  57. {
  58. return 'get' . $this->getColumnForParameter('slug_column')->getPhpName();
  59. }
  60. /**
  61. * Get the setter of the column of the behavior
  62. *
  63. * @return string The related setter, e.g. 'setSlug'
  64. */
  65. protected function getColumnSetter()
  66. {
  67. return 'set' . $this->getColumnForParameter('slug_column')->getPhpName();
  68. }
  69. /**
  70. * Add code in ObjectBuilder::preSave
  71. *
  72. * @return string The code to put at the hook
  73. */
  74. public function preSave(PHP5ObjectBuilder $builder)
  75. {
  76. $const = $builder->getColumnConstant($this->getColumnForParameter('slug_column'));
  77. $pattern = $this->getParameter('slug_pattern');
  78. $script = "
  79. if (\$this->isColumnModified($const) && \$this->{$this->getColumnGetter()}()) {
  80. \$this->{$this->getColumnSetter()}(\$this->makeSlugUnique(\$this->{$this->getColumnGetter()}()));";
  81. if ($pattern && false === $this->booleanValue($this->getParameter('permanent'))) {
  82. $script .= "
  83. } elseif (";
  84. $count = preg_match_all('/{([a-zA-Z]+)}/', $pattern, $matches, PREG_PATTERN_ORDER);
  85. foreach ($matches[1] as $key => $match) {
  86. $columnName = $this->underscore(ucfirst($match));
  87. $column = $this->getTable()->getColumn($columnName);
  88. if ((null == $column) && $this->getTable()->hasBehavior('symfony_i18n')) {
  89. $i18n = $this->getTable()->getBehavior('symfony_i18n');
  90. $column = $i18n->getI18nTable()->getColumn($columnName);
  91. }
  92. if (null == $column) {
  93. throw new \InvalidArgumentException(sprintf('The pattern %s is invalid the column %s is not found', $pattern, $match));
  94. }
  95. $columnConst = $builder->getColumnConstant($column);
  96. $script .= "\$this->isColumnModified($columnConst)" . ($key < $count - 1 ? " || " : "");
  97. }
  98. $script .= ") {
  99. \$this->{$this->getColumnSetter()}(\$this->createSlug());";
  100. }
  101. if (null == $pattern && false === $this->booleanValue($this->getParameter('permanent'))) {
  102. $script .= "
  103. } else {
  104. \$this->{$this->getColumnSetter()}(\$this->createSlug());
  105. }";
  106. } else {
  107. $script .= "
  108. } elseif (!\$this->{$this->getColumnGetter()}()) {
  109. \$this->{$this->getColumnSetter()}(\$this->createSlug());
  110. }";
  111. }
  112. return $script;
  113. }
  114. public function objectMethods(PHP5ObjectBuilder $builder)
  115. {
  116. $this->builder = $builder;
  117. $script = '';
  118. if ('slug' != $this->getParameter('slug_column')) {
  119. $this->addSlugSetter($script);
  120. $this->addSlugGetter($script);
  121. }
  122. $this->addCreateSlug($script);
  123. $this->addCreateRawSlug($script);
  124. if ($this->booleanValue($this->getParameter('add_cleanup'))) {
  125. $this->addCleanupSlugPart($script);
  126. }
  127. $this->addLimitSlugSize($script);
  128. $this->addMakeSlugUnique($script);
  129. return $script;
  130. }
  131. protected function addSlugSetter(&$script)
  132. {
  133. $script .= "
  134. /**
  135. * Wrap the setter for slug value
  136. *
  137. * @param string
  138. * @return " . $this->getTable()->getPhpName() . "
  139. */
  140. public function setSlug(\$v)
  141. {
  142. return \$this->" . $this->getColumnSetter() . "(\$v);
  143. }
  144. ";
  145. }
  146. protected function addSlugGetter(&$script)
  147. {
  148. $script .= "
  149. /**
  150. * Wrap the getter for slug value
  151. *
  152. * @return string
  153. */
  154. public function getSlug()
  155. {
  156. return \$this->" . $this->getColumnGetter() . "();
  157. }
  158. ";
  159. }
  160. protected function addCreateSlug(&$script)
  161. {
  162. $script .= "
  163. /**
  164. * Create a unique slug based on the object
  165. *
  166. * @return string The object slug
  167. */
  168. protected function createSlug()
  169. {
  170. \$slug = \$this->createRawSlug();
  171. \$slug = \$this->limitSlugSize(\$slug);
  172. \$slug = \$this->makeSlugUnique(\$slug);
  173. return \$slug;
  174. }
  175. ";
  176. }
  177. protected function addCreateRawSlug(&$script)
  178. {
  179. $pattern = $this->getParameter('slug_pattern');
  180. $script .= "
  181. /**
  182. * Create the slug from the appropriate columns
  183. *
  184. * @return string
  185. */
  186. protected function createRawSlug()
  187. {
  188. ";
  189. if ($pattern) {
  190. $script .= "return '" . str_replace(array('{', '}'), array('\' . $this->cleanupSlugPart($this->get', '()) . \''), $pattern) . "';";
  191. } else {
  192. $script .= "return \$this->cleanupSlugPart(\$this->__toString());";
  193. }
  194. $script .= "
  195. }
  196. ";
  197. return $script;
  198. }
  199. public function addCleanupSlugPart(&$script)
  200. {
  201. $script .= "
  202. /**
  203. * Cleanup a string to make a slug of it
  204. * Removes special characters, replaces blanks with a separator, and trim it
  205. *
  206. * @param string \$slug the text to slugify
  207. * @param string \$replacement the separator used by slug
  208. * @return string the slugified text
  209. */
  210. protected static function cleanupSlugPart(\$slug, \$replacement = '" . $this->getParameter('replacement') . "')
  211. {
  212. // transliterate
  213. if (function_exists('iconv')) {
  214. \$slug = iconv('utf-8', 'us-ascii//TRANSLIT', \$slug);
  215. }
  216. // lowercase
  217. if (function_exists('mb_strtolower')) {
  218. \$slug = mb_strtolower(\$slug);
  219. } else {
  220. \$slug = strtolower(\$slug);
  221. }
  222. // remove accents resulting from OSX's iconv
  223. \$slug = str_replace(array('\'', '`', '^'), '', \$slug);
  224. // replace non letter or digits with separator
  225. \$slug = preg_replace('" . $this->getParameter('replace_pattern') . "', \$replacement, \$slug);
  226. // trim
  227. \$slug = trim(\$slug, \$replacement);
  228. if (empty(\$slug)) {
  229. return 'n-a';
  230. }
  231. return \$slug;
  232. }
  233. ";
  234. }
  235. public function addLimitSlugSize(&$script)
  236. {
  237. $size = $this->getColumnForParameter('slug_column')->getSize();
  238. $script .= "
  239. /**
  240. * Make sure the slug is short enough to accommodate the column size
  241. *
  242. * @param string \$slug the slug to check
  243. * @param int \$incrementReservedSpace the number of characters to keep empty
  244. *
  245. * @return string the truncated slug
  246. */
  247. protected static function limitSlugSize(\$slug, \$incrementReservedSpace = 3)
  248. {
  249. // check length, as suffix could put it over maximum
  250. if (strlen(\$slug) > ($size - \$incrementReservedSpace)) {
  251. \$slug = substr(\$slug, 0, $size - \$incrementReservedSpace);
  252. }
  253. return \$slug;
  254. }
  255. ";
  256. }
  257. public function addMakeSlugUnique(&$script)
  258. {
  259. $script .= "
  260. /**
  261. * Get the slug, ensuring its uniqueness
  262. *
  263. * @param string \$slug the slug to check
  264. * @param string \$separator the separator used by slug
  265. * @param int \$alreadyExists false for the first try, true for the second, and take the high count + 1
  266. * @return string the unique slug
  267. */
  268. protected function makeSlugUnique(\$slug, \$separator = '" . $this->getParameter('separator') . "', \$alreadyExists = false)
  269. {";
  270. $getter = $this->getColumnGetter();
  271. $script .= "
  272. if (!\$alreadyExists) {
  273. \$slug2 = \$slug;
  274. } else {
  275. \$slug2 = \$slug . \$separator;";
  276. if (null == $this->getParameter('slug_pattern')) {
  277. $script .= "
  278. \$count = " . $this->builder->getStubQueryBuilder()->getClassname() . "::create()
  279. ->filterBySlug(\$this->$getter())
  280. ->filterByPrimaryKey(\$this->getPrimaryKey())
  281. ->count();
  282. if (1 == \$count) {
  283. return \$this->$getter();
  284. }";
  285. }
  286. $script .= "
  287. }
  288. \$query = " . $this->builder->getStubQueryBuilder()->getClassname() . "::create('q')
  289. ";
  290. $platform = $this->getTable()->getDatabase()->getPlatform();
  291. if ($platform instanceof PgsqlPlatform) {
  292. $script .= "->where('q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? '~*' : '=') . ' ?', \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
  293. } elseif ($platform instanceof MssqlPlatform) {
  294. $script .= "->where('q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? 'like' : '=') . ' ?', \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
  295. } elseif ($platform instanceof OraclePlatform) {
  296. $script .= "->where((\$alreadyExists ? 'REGEXP_LIKE(' : '') . 'q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? ',' : '=') . ' ?' . (\$alreadyExists ? ')' : ''), \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
  297. } else {
  298. $script .= "->where('q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? 'REGEXP' : '=') . ' ?', \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
  299. }
  300. $script .="->prune(\$this)";
  301. if ($this->getParameter('scope_column')) {
  302. $scopeGetter = 'get' . $this->getColumnForParameter('scope_column')->getPhpName();
  303. $script .= "
  304. ->filterBy('{$this->getColumnForParameter('scope_column')->getPhpName()}', \$this->{$scopeGetter}())";
  305. }
  306. // watch out: some of the columns may be hidden by the soft_delete behavior
  307. if ($this->table->hasBehavior('soft_delete')) {
  308. $script .= "
  309. ->includeDeleted()";
  310. }
  311. $script .= "
  312. ;
  313. if (!\$alreadyExists) {
  314. \$count = \$query->count();
  315. if (\$count > 0) {
  316. return \$this->makeSlugUnique(\$slug, \$separator, true);
  317. }
  318. return \$slug2;
  319. }
  320. // Already exists
  321. \$object = \$query
  322. ->addDescendingOrderByColumn('LENGTH(" . $this->getColumnForParameter('slug_column')->getName() . ")')
  323. ->addDescendingOrderByColumn('" . $this->getColumnForParameter('slug_column')->getName() . "')
  324. ->findOne();
  325. // First duplicate slug
  326. if (null == \$object) {
  327. return \$slug2 . '1';
  328. }
  329. \$slugNum = substr(\$object->" . $getter . "(), strlen(\$slug) + 1);
  330. if ('0' === \$slugNum[0]) {
  331. \$slugNum[0] = 1;
  332. }
  333. return \$slug2 . (\$slugNum + 1);
  334. }
  335. ";
  336. }
  337. public function queryMethods(QueryBuilder $builder)
  338. {
  339. $this->builder = $builder;
  340. $script = '';
  341. if ($this->getParameter('slug_column') != 'slug') {
  342. $this->addFilterBySlug($script);
  343. $this->addFindOneBySlug($script);
  344. }
  345. return $script;
  346. }
  347. protected function addFilterBySlug(&$script)
  348. {
  349. $script .= "
  350. /**
  351. * Filter the query on the slug column
  352. *
  353. * @param string \$slug The value to use as filter.
  354. *
  355. * @return " . $this->builder->getStubQueryBuilder()->getClassname() . " The current query, for fluid interface
  356. */
  357. public function filterBySlug(\$slug)
  358. {
  359. return \$this->addUsingAlias(" . $this->builder->getColumnConstant($this->getColumnForParameter('slug_column')) . ", \$slug, Criteria::EQUAL);
  360. }
  361. ";
  362. }
  363. protected function addFindOneBySlug(&$script)
  364. {
  365. $script .= "
  366. /**
  367. * Find one object based on its slug
  368. *
  369. * @param string \$slug The value to use as filter.
  370. * @param PropelPDO \$con The optional connection object
  371. *
  372. * @return " . $this->builder->getStubObjectBuilder()->getClassname() . " the result, formatted by the current formatter
  373. */
  374. public function findOneBySlug(\$slug, \$con = null)
  375. {
  376. return \$this->filterBySlug(\$slug)->findOne(\$con);
  377. }
  378. ";
  379. }
  380. /**
  381. * @param string $string
  382. *
  383. * @return string
  384. */
  385. protected function underscore($string)
  386. {
  387. return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($string, '_', '.')));
  388. }
  389. }