inflector.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. /**
  14. * Some of this code was written by Flinn Mueller.
  15. *
  16. * @package Fuel
  17. * @category Core
  18. * @copyright Flinn Mueller
  19. * @link http://docs.fuelphp.com/classes/inlector.html
  20. */
  21. class Inflector
  22. {
  23. protected static $uncountable_words = array(
  24. 'equipment', 'information', 'rice', 'money',
  25. 'species', 'series', 'fish', 'meta'
  26. );
  27. protected static $plural_rules = array(
  28. '/^(ox)$/i' => '\1\2en', // ox
  29. '/([m|l])ouse$/i' => '\1ice', // mouse, louse
  30. '/(matr|vert|ind)ix|ex$/i' => '\1ices', // matrix, vertex, index
  31. '/(x|ch|ss|sh)$/i' => '\1es', // search, switch, fix, box, process, address
  32. '/([^aeiouy]|qu)y$/i' => '\1ies', // query, ability, agency
  33. '/(hive)$/i' => '\1s', // archive, hive
  34. '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', // half, safe, wife
  35. '/sis$/i' => 'ses', // basis, diagnosis
  36. '/([ti])um$/i' => '\1a', // datum, medium
  37. '/(p)erson$/i' => '\1eople', // person, salesperson
  38. '/(m)an$/i' => '\1en', // man, woman, spokesman
  39. '/(c)hild$/i' => '\1hildren', // child
  40. '/(buffal|tomat)o$/i' => '\1\2oes', // buffalo, tomato
  41. '/(bu|campu)s$/i' => '\1\2ses', // bus, campus
  42. '/(alias|status|virus)$/i' => '\1es', // alias
  43. '/(octop)us$/i' => '\1i', // octopus
  44. '/(ax|cris|test)is$/i' => '\1es', // axis, crisis
  45. '/s$/' => 's', // no change (compatibility)
  46. '/$/' => 's',
  47. );
  48. protected static $singular_rules = array(
  49. '/(matr)ices$/i' => '\1ix',
  50. '/(vert|ind)ices$/i' => '\1ex',
  51. '/^(ox)en/i' => '\1',
  52. '/(alias)es$/i' => '\1',
  53. '/([octop|vir])i$/i' => '\1us',
  54. '/(cris|ax|test)es$/i' => '\1is',
  55. '/(shoe)s$/i' => '\1',
  56. '/(o)es$/i' => '\1',
  57. '/(bus|campus)es$/i' => '\1',
  58. '/([m|l])ice$/i' => '\1ouse',
  59. '/(x|ch|ss|sh)es$/i' => '\1',
  60. '/(m)ovies$/i' => '\1\2ovie',
  61. '/(s)eries$/i' => '\1\2eries',
  62. '/([^aeiouy]|qu)ies$/i' => '\1y',
  63. '/([lr])ves$/i' => '\1f',
  64. '/(tive)s$/i' => '\1',
  65. '/(hive)s$/i' => '\1',
  66. '/([^f])ves$/i' => '\1fe',
  67. '/(^analy)ses$/i' => '\1sis',
  68. '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
  69. '/([ti])a$/i' => '\1um',
  70. '/(p)eople$/i' => '\1\2erson',
  71. '/(m)en$/i' => '\1an',
  72. '/(s)tatuses$/i' => '\1\2tatus',
  73. '/(c)hildren$/i' => '\1\2hild',
  74. '/(n)ews$/i' => '\1\2ews',
  75. '/([^us])s$/i' => '\1',
  76. );
  77. /**
  78. * Add order suffix to numbers ex. 1st 2nd 3rd 4th 5th
  79. *
  80. * @param int the number to ordinalize
  81. * @return string the ordinalized version of $number
  82. * @link http://snipplr.com/view/4627/a-function-to-add-a-prefix-to-numbers-ex-1st-2nd-3rd-4th-5th/
  83. */
  84. public static function ordinalize($number)
  85. {
  86. if ( ! is_numeric($number))
  87. {
  88. return $number;
  89. }
  90. if (in_array(($number % 100), range(11, 13)))
  91. {
  92. return $number . 'th';
  93. }
  94. else
  95. {
  96. switch ($number % 10)
  97. {
  98. case 1:
  99. return $number . 'st';
  100. break;
  101. case 2:
  102. return $number . 'nd';
  103. break;
  104. case 3:
  105. return $number . 'rd';
  106. break;
  107. default:
  108. return $number . 'th';
  109. break;
  110. }
  111. }
  112. }
  113. /**
  114. * Gets the plural version of the given word
  115. *
  116. * @param string the word to pluralize
  117. * @param int number of instances
  118. * @return string the plural version of $word
  119. */
  120. public static function pluralize($word, $count = 0)
  121. {
  122. $result = strval($word);
  123. // If a counter is provided, and that equals 1
  124. // return as singular.
  125. if ($count === 1)
  126. {
  127. return $result;
  128. }
  129. if ( ! static::is_countable($result))
  130. {
  131. return $result;
  132. }
  133. foreach (static::$plural_rules as $rule => $replacement)
  134. {
  135. if (preg_match($rule, $result))
  136. {
  137. $result = preg_replace($rule, $replacement, $result);
  138. break;
  139. }
  140. }
  141. return $result;
  142. }
  143. /**
  144. * Gets the singular version of the given word
  145. *
  146. * @param string the word to singularize
  147. * @return string the singular version of $word
  148. */
  149. public static function singularize($word)
  150. {
  151. $result = strval($word);
  152. if ( ! static::is_countable($result))
  153. {
  154. return $result;
  155. }
  156. foreach (static::$singular_rules as $rule => $replacement)
  157. {
  158. if (preg_match($rule, $result))
  159. {
  160. $result = preg_replace($rule, $replacement, $result);
  161. break;
  162. }
  163. }
  164. return $result;
  165. }
  166. /**
  167. * Takes a string that has words seperated by underscores and turns it into
  168. * a CamelCased string.
  169. *
  170. * @param string the underscored word
  171. * @return string the CamelCased version of $underscored_word
  172. */
  173. public static function camelize($underscored_word)
  174. {
  175. return preg_replace('/(^|_)(.)/e', "strtoupper('\\2')", strval($underscored_word));
  176. }
  177. /**
  178. * Takes a CamelCased string and returns an underscore separated version.
  179. *
  180. * @param string the CamelCased word
  181. * @return string an underscore separated version of $camel_cased_word
  182. */
  183. public static function underscore($camel_cased_word)
  184. {
  185. return \Str::lower(preg_replace('/([A-Z]+)([A-Z])/', '\1_\2', preg_replace('/([a-z\d])([A-Z])/', '\1_\2', strval($camel_cased_word))));
  186. }
  187. /**
  188. * Translate string to 7-bit ASCII
  189. * Only works with UTF-8.
  190. *
  191. * @param string $str string to translate
  192. * @param bool $allow_non_ascii wether to remove non ascii
  193. * @return string translated string
  194. */
  195. public static function ascii($str, $allow_non_ascii = false)
  196. {
  197. // Translate unicode characters to their simpler counterparts
  198. \Config::load('ascii', true);
  199. $foreign_characters = \Config::get('ascii');
  200. $str = preg_replace(array_keys($foreign_characters), array_values($foreign_characters), $str);
  201. if ( ! $allow_non_ascii)
  202. {
  203. return preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', $str);
  204. }
  205. return $str;
  206. }
  207. /**
  208. * Converts your text to a URL-friendly title so it can be used in the URL.
  209. * Only works with UTF8 input and and only outputs 7 bit ASCII characters.
  210. *
  211. * @param string $str the text
  212. * @param string $sep the separator (either - or _)
  213. * @param bool $lowercase wether to convert to lowercase
  214. * @param bool $allow_non_ascii wether to allow non ascii
  215. * @return string the new title
  216. */
  217. public static function friendly_title($str, $sep = '-', $lowercase = false, $allow_non_ascii = false)
  218. {
  219. // Allow underscore, otherwise default to dash
  220. $sep = $sep === '_' ? '_' : '-';
  221. // Remove tags
  222. $str = \Security::strip_tags($str);
  223. // Decode all entities to their simpler forms
  224. $str = html_entity_decode($str, ENT_QUOTES, 'UTF-8');
  225. // Remove all quotes.
  226. $str = preg_replace("#[\"\']#", '', $str);
  227. // Only allow 7bit characters
  228. $str = static::ascii($str, $allow_non_ascii);
  229. if ($allow_non_ascii)
  230. {
  231. // Strip regular special chars.
  232. $str = preg_replace("#[\.;:'\"\]\}\[\{\+\)\(\*&\^\$\#@\!±`%~']#iu", '', $str);
  233. }
  234. else
  235. {
  236. // Strip unwanted characters
  237. $str = preg_replace("#[^a-z0-9]#i", $sep, $str);
  238. }
  239. $str = preg_replace("#[/_|+ -]+#u", $sep, $str);
  240. $str = trim($str, $sep);
  241. if ($lowercase === true)
  242. {
  243. $str = \Str::lower($str);
  244. }
  245. return $str;
  246. }
  247. /**
  248. * Turns an underscore or dash separated word and turns it into a human looking string.
  249. *
  250. * @param string the word
  251. * @param string the separator (either _ or -)
  252. * @param bool lowercare string and upper case first
  253. * @return string the human version of given string
  254. */
  255. public static function humanize($str, $sep = '_', $lowercase = true)
  256. {
  257. // Allow dash, otherwise default to underscore
  258. $sep = $sep != '-' ? '_' : $sep;
  259. if ($lowercase === true)
  260. {
  261. $str = \Str::ucfirst($str);
  262. }
  263. return str_replace($sep, " ", strval($str));
  264. }
  265. /**
  266. * Takes the class name out of a modulized string.
  267. *
  268. * @param string the modulized class
  269. * @return string the string without the class name
  270. */
  271. public static function demodulize($class_name_in_module)
  272. {
  273. return preg_replace('/^.*::/', '', strval($class_name_in_module));
  274. }
  275. /**
  276. * Takes the namespace off the given class name.
  277. *
  278. * @param string the class name
  279. * @return string the string without the namespace
  280. */
  281. public static function denamespace($class_name)
  282. {
  283. $class_name = trim($class_name, '\\');
  284. if ($last_separator = strrpos($class_name, '\\'))
  285. {
  286. $class_name = substr($class_name, $last_separator + 1);
  287. }
  288. return $class_name;
  289. }
  290. /**
  291. * Returns the namespace of the given class name.
  292. *
  293. * @param string $class_name the class name
  294. * @return string the string without the namespace
  295. */
  296. public static function get_namespace($class_name)
  297. {
  298. $class_name = trim($class_name, '\\');
  299. if ($last_separator = strrpos($class_name, '\\'))
  300. {
  301. return substr($class_name, 0, $last_separator + 1);
  302. }
  303. return '';
  304. }
  305. /**
  306. * Takes a class name and determines the table name. The table name is a
  307. * pluralized version of the class name.
  308. *
  309. * @param string the table name
  310. * @return string the table name
  311. */
  312. public static function tableize($class_name)
  313. {
  314. $class_name = static::denamespace($class_name);
  315. if (strncasecmp($class_name, 'Model_', 6) === 0)
  316. {
  317. $class_name = substr($class_name, 6);
  318. }
  319. return \Str::lower(static::pluralize(static::underscore($class_name)));
  320. }
  321. /**
  322. * Takes an underscored classname and uppercases all letters after the underscores.
  323. *
  324. * @param string classname
  325. * @param string separator
  326. * @return string
  327. */
  328. public static function words_to_upper($class, $sep = '_')
  329. {
  330. return str_replace(' ', $sep, ucwords(str_replace($sep, ' ', $class)));
  331. }
  332. /**
  333. * Takes a table name and creates the class name.
  334. *
  335. * @param string the table name
  336. * @param bool whether to singularize the table name or not
  337. * @return string the class name
  338. */
  339. public static function classify($name, $force_singular = true)
  340. {
  341. $class = ($force_singular) ? static::singularize($name) : $name;
  342. return static::words_to_upper($class);
  343. }
  344. /**
  345. * Gets the foreign key for a given class.
  346. *
  347. * @param string the class name
  348. * @param bool $use_underscore whether to use an underscore or not
  349. * @return string the foreign key
  350. */
  351. public static function foreign_key($class_name, $use_underscore = true)
  352. {
  353. $class_name = static::denamespace(\Str::lower($class_name));
  354. if (strncasecmp($class_name, 'Model_', 6) === 0)
  355. {
  356. $class_name = substr($class_name, 6);
  357. }
  358. return static::underscore(static::demodulize($class_name)).($use_underscore ? "_id" : "id");
  359. }
  360. /**
  361. * Checks if the given word has a plural version.
  362. *
  363. * @param string the word to check
  364. * @return bool if the word is countable
  365. */
  366. public static function is_countable($word)
  367. {
  368. return ! (\in_array(\Str::lower(\strval($word)), static::$uncountable_words));
  369. }
  370. }