* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace ICanBoogie; /** * A representation of the inflections used by an inflector. * * @property-read array $plurals Rules for {@link pluralize()}. * @property-read array $singulars Rules for {@link singularize()}. * @property-read array $uncountables Uncountables. * @property-read array $humans Rules for {@link humanize()}. * @property-read array $acronyms Acronyms. * @property-read array $acronym_regex Acronyms regex. */ class Inflections { static private $inflections = array(); /** * Returns inflections for the specified locale. * * Note: Inflections are shared for the same locale. If you need to alter an instance you * SHOULD clone it first, otherwise your changes will affect others. * * @param string $locale * * @return \ICanBoogie\Inflections */ static public function get($locale='en') { if (isset(self::$inflections[$locale])) { return self::$inflections[$locale]; } $instance = new static; $inflections = require __DIR__ . "/inflections/{$locale}.php"; $inflections($instance); return self::$inflections[$locale] = $instance; } /** * Rules for {@link pluralize()}. * * @var array[string]string */ protected $plurals = array(); /** * Rules for {@link singularize()}. * * @var array[string]string */ protected $singulars = array(); /** * Uncountables. * * @var array[]string */ protected $uncountables = array(); /** * Rules for {@link humanize()}. * * @var array[string]string */ protected $humans = array(); /** * Acronyms. * * @var array[string]string */ protected $acronyms = array(); /** * Acronyms regex. * * @var string */ protected $acronym_regex = '/(?=a)b/'; /** * Returns the {@link $acronyms}, {@link $acronym_regex}, {@link $plurals}, {@link $singulars}, * {@link $uncountables} and {@link $humans} properties. * * @param string $property * * @throws PropertyNotDefined in attempt to read an unaccessible property. If the {@link PropertyNotDefined} * class is not available a {@link \InvalidArgumentException} is thrown instead. */ public function __get($property) { static $readers = array('acronyms', 'acronym_regex', 'plurals', 'singulars', 'uncountables', 'humans'); if (in_array($property, $readers)) { return $this->$property; } if (class_exists('ICanBoogie\PropertyNotDefined')) { throw new PropertyNotDefined(array($property, $this)); } else { throw new \InvalidArgumentException("Property not defined: $property"); } } /** * Specifies a new acronym. An acronym must be specified as it will appear * in a camelized string. An underscore string that contains the acronym * will retain the acronym when passed to {@link camelize}, {@link humanize}, or * {@link titleize}. A camelized string that contains the acronym will maintain * the acronym when titleized or humanized, and will convert the acronym * into a non-delimited single lowercase word when passed to {@link underscore}. * *
	 * $this->acronym('HTML');
	 * $this->titleize('html');                 // 'HTML'
	 * $this->camelize('html');                 // 'HTML'
	 * $this->underscore('MyHTML');             // 'my_html'
	 * 
* * The acronym, however, must occur as a delimited unit and not be part of * another word for conversions to recognize it: * *
	 * $this->acronym('HTTP');
	 * $this->camelize('my_http_delimited');    // 'MyHTTPDelimited'
	 * $this->camelize('https');                // 'Https', not 'HTTPs'
	 * $this->underscore('HTTPS');              // 'http_s', not 'https'
	 *
	 * $this->acronym('HTTPS');
	 * $this->camelize('https');                // 'HTTPS'
	 * $this->underscore('HTTPS');              // 'https'
	 * 
* * Note: Acronyms that are passed to {@link pluralize} will no longer be * recognized, since the acronym will not occur as a delimited unit in the * pluralized result. To work around this, you must specify the pluralized * form as an acronym as well: * *
	 * $this->acronym('API');
	 * $this->camelize($this->pluralize('api')); // 'Apis'
	 *
	 * $this->acronym('APIs');
	 * $this->camelize($this->pluralize('api')); // 'APIs'
	 * 
* * {@link acronym} may be used to specify any word that contains an acronym or * otherwise needs to maintain a non-standard capitalization. The only * restriction is that the word must begin with a capital letter. * *
	 * $this->acronym('RESTful');
	 * $this->underscore('RESTful');             // 'restful'
	 * $this->underscore('RESTfulController');   // 'restful_controller'
	 * $this->titleize('RESTfulController');     // 'RESTful Controller'
	 * $this->camelize('restful');               // 'RESTful'
	 * $this->camelize('restful_controller');    // 'RESTfulController'
	 *
	 * $this->acronym('McHammer');
	 * $this->underscore('McHammer');            // 'mchammer'
	 * $this->camelize('mchammer');              // 'McHammer'
	 * 
*/ public function acronym($word) { $this->acronyms[downcase($word)] = $word; $this->acronym_regex = '/' . implode('|', $this->acronyms) . '/'; return $this; } /** * Specifies a new pluralization rule and its replacement. * *
	 * $this->plural('/^(ax|test)is$/i', '\1es');
	 * $this->plural('/(buffal|tomat)o$/i', '\1oes');
	 * $this->plural('/^(m|l)ouse$/i', '\1ice');
	 * 
* * @param string $rule A regex string. * @param string $replacement The replacement should always be a string that may include * references to the matched data from the rule. */ public function plural($rule, $replacement) { unset($this->uncountables[$rule]); unset($this->uncountables[$replacement]); $this->plurals = array($rule => $replacement) + $this->plurals; return $this; } /** * Specifies a new singularization rule and its replacement. * *
	 * $this->singular('/(n)ews$/i', '\1ews');
	 * $this->singular('/([^aeiouy]|qu)ies$/i', '\1y');
	 * $this->singular('/(quiz)zes$/i', '\1');
	 * 
* * @param string $rule A regex string. * @param string $replacement The replacement should always be a string that may include * references to the matched data from the rule. */ public function singular($rule, $replacement) { unset($this->uncountables[$rule]); unset($this->uncountables[$replacement]); $this->singulars = array($rule => $replacement) + $this->singulars; return $this; } /** * Specifies a new irregular that applies to both pluralization and singularization at the * same time. This can only be used for strings, not regular expressions. You simply pass * the irregular in singular and plural form. * *
	 * $this->irregular('child', 'children');
	 * $this->irregular('person', 'people');
	 * 
* * @param string $singular * @param string $plural */ public function irregular($singular, $plural) { unset($this->uncountables[$singular]); unset($this->uncountables[$plural]); $s0 = mb_substr($singular, 0, 1); $s0_upcase = upcase($s0); $srest = mb_substr($singular, 1); $p0 = mb_substr($plural, 0, 1); $p0_upcase = upcase($p0); $prest = mb_substr($plural, 1); if ($s0_upcase == $p0_upcase) { $this->plural("/({$s0}){$srest}$/i", '\1' . $prest); $this->plural("/({$p0}){$prest}$/i", '\1' . $prest); $this->singular("/({$s0}){$srest}$/i", '\1' . $srest); $this->singular("/({$p0}){$prest}$/i", '\1' . $srest); } else { $s0_downcase = downcase($s0); $p0_downcase = downcase($p0); $this->plural("/{$s0_upcase}(?i){$srest}$/", $p0_upcase . $prest); $this->plural("/{$s0_downcase}(?i){$srest}$/", $p0_downcase . $prest); $this->plural("/{$p0_upcase}(?i){$prest}$/", $p0_upcase . $prest); $this->plural("/{$p0_downcase}(?i){$prest}$/", $p0_downcase . $prest); $this->singular("/{$s0_upcase}(?i){$srest}$/", $s0_upcase . $srest); $this->singular("/{$s0_downcase}(?i){$srest}$/", $s0_downcase . $srest); $this->singular("/{$p0_upcase}(?i){$prest}$/", $s0_upcase . $srest); $this->singular("/{$p0_downcase}(?i){$prest}$/", $s0_downcase . $srest); } return $this; } /** * Add uncountable words that shouldn't be attempted inflected. * *
	 * $this->uncountable('money');
	 * $this->uncountable(explode(' ', 'money information rice'));
	 * 
* * @param string|array $word */ public function uncountable($word) { if (is_array($word)) { $this->uncountables += array_combine($word, $word); return; } $this->uncountables[$word] = $word; return $this; } /** * Specifies a humanized form of a string by a regular expression rule or by a string mapping. * When using a regular expression based replacement, the normal humanize formatting is * called after the replacement. When a string is used, the human form should be specified * as desired (example: 'The name', not 'the_name'). * *
	 * $this->human('/_cnt$/i', '\1_count');
	 * $this->human('legacy_col_person_name', 'Name');
	 * 
* * @param string $rule A regular expression rule or a string mapping. Strings that starts with * "/", "#" or "~" are recognized as regular expressions. * * @param string $replacement */ public function human($rule, $replacement) { $r0 = $rule{0}; if ($r0 != '/' && $r0 != '#' && $r0 != '~') { $rule = '/' . preg_quote($rule, '/') . '/'; } $this->humans = array($rule => $replacement) + $this->humans; return $this; } }