diff --git a/application/config/strings.php b/application/config/strings.php index f2c8960c..50f20f2d 100644 --- a/application/config/strings.php +++ b/application/config/strings.php @@ -11,22 +11,90 @@ | the "singular" and "plural" methods on the Str class to convert a given | word from singular to plural and vice versa. | - | This simple array is in constrast to the complicated regular expression - | patterns used by other frameworks. We think you'll enjoy the speed and - | simplicity of this solution. - | - | When adding a word to the array, the key should be the singular form, - | while the array value should be the plural form. We've included an - | example to get you started! + | Note that the regular expressions are only for inflecting English words. + | To inflect a non-English string, simply add its singular and plural + | form to the array of "irregular" word forms. | */ - 'inflection' => array( + 'plural' => array( + '/(quiz)$/i' => "$1zes", + '/^(ox)$/i' => "$1en", + '/([m|l])ouse$/i' => "$1ice", + '/(matr|vert|ind)ix|ex$/i' => "$1ices", + '/(x|ch|ss|sh)$/i' => "$1es", + '/([^aeiouy]|qu)y$/i' => "$1ies", + '/(hive)$/i' => "$1s", + '/(?:([^f])fe|([lr])f)$/i' => "$1$2ves", + '/(shea|lea|loa|thie)f$/i' => "$1ves", + '/sis$/i' => "ses", + '/([ti])um$/i' => "$1a", + '/(tomat|potat|ech|her|vet)o$/i' => "$1oes", + '/(bu)s$/i' => "$1ses", + '/(alias)$/i' => "$1es", + '/(octop)us$/i' => "$1i", + '/(ax|test)is$/i' => "$1es", + '/(us)$/i' => "$1es", + '/s$/i' => "s", + '/$/' => "s" + ), - 'user' => 'users', - 'person' => 'people', - 'comment' => 'comments', + 'singular' => array( + '/(quiz)zes$/i' => "$1", + '/(matr)ices$/i' => "$1ix", + '/(vert|ind)ices$/i' => "$1ex", + '/^(ox)en$/i' => "$1", + '/(alias)es$/i' => "$1", + '/(octop|vir)i$/i' => "$1us", + '/(cris|ax|test)es$/i' => "$1is", + '/(shoe)s$/i' => "$1", + '/(o)es$/i' => "$1", + '/(bus)es$/i' => "$1", + '/([m|l])ice$/i' => "$1ouse", + '/(x|ch|ss|sh)es$/i' => "$1", + '/(m)ovies$/i' => "$1ovie", + '/(s)eries$/i' => "$1eries", + '/([^aeiouy]|qu)ies$/i' => "$1y", + '/([lr])ves$/i' => "$1f", + '/(tive)s$/i' => "$1", + '/(hive)s$/i' => "$1", + '/(li|wi|kni)ves$/i' => "$1fe", + '/(shea|loa|lea|thie)ves$/i' => "$1f", + '/(^analy)ses$/i' => "$1sis", + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => "$1$2sis", + '/([ti])a$/i' => "$1um", + '/(n)ews$/i' => "$1ews", + '/(h|bl)ouses$/i' => "$1ouse", + '/(corpse)s$/i' => "$1", + '/(us)es$/i' => "$1", + '/(us|ss)$/i' => "$1", + '/s$/i' => "", + ), + 'irregular' => array( + 'child' => 'children', + 'foot' => 'feet', + 'goose' => 'geese', + 'man' => 'men', + 'move' => 'moves', + 'person' => 'people', + 'sex' => 'sexes', + 'tooth' => 'teeth', + ), + + 'uncountable' => array( + 'audio', + 'equipment', + 'deer', + 'fish', + 'gold', + 'information', + 'money', + 'rice', + 'police', + 'series', + 'sheep', + 'species', ), /* diff --git a/laravel/database/connection.php b/laravel/database/connection.php index f38f9873..5b2ec0e9 100644 --- a/laravel/database/connection.php +++ b/laravel/database/connection.php @@ -84,6 +84,33 @@ protected function grammar() } } + /** + * Execute a callback wrapped in a database transaction. + * + * @param Closure $callback + * @return void + */ + public function transaction($callback) + { + $this->pdo->beginTransaction(); + + // After beginning the database transaction, we will call the Closure + // so that it can do its database work. If an exception occurs we'll + // rollback the transaction and re-throw back to the developer. + try + { + call_user_func($callback); + } + catch (\Exception $e) + { + $this->pdo->rollBack(); + + throw $e; + } + + $this->pdo->commit(); + } + /** * Execute a SQL query against the connection and return a single column result. * diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index e09db992..ab3bd95c 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -1,5 +1,6 @@ fill($attributes); } - /** - * Set the accessible attributes for the given model. - * - * @param array $attributes - * @return void - */ - public static function accessible($attributes) - { - static::$accessible = $attributes; - } - /** * Hydrate the model with an array of attributes. * @@ -157,6 +147,17 @@ public function fill($attributes) return $this; } + /** + * Set the accessible attributes for the given model. + * + * @param array $attributes + * @return void + */ + public static function accessible($attributes) + { + static::$accessible = $attributes; + } + /** * Create a new model and store it in the database. * @@ -211,9 +212,7 @@ public static function find($id, $columns = array('*')) */ public static function all() { - $model = new static; - - return $model->query()->get(); + return with(new static)->query()->get(); } /** @@ -420,6 +419,16 @@ public function dirty() return ! $this->exists or $this->original !== $this->attributes; } + /** + * Get the name of the table associated with the model. + * + * @return string + */ + public function table() + { + return static::$table ?: strtolower(Str::plural(basename(get_class($this)))); + } + /** * Get the dirty attributes for the model. * @@ -501,6 +510,14 @@ public function __get($key) return $this->relationships[$key]; } + // Next we'll check if the requested key is in the array of attributes + // for the model. These are simply regular properties that typically + // correspond to a single column on the database for the model. + elseif (array_key_exists($key, $this->attributes)) + { + return $this->{"get_{$key}"}(); + } + // If the item is not a loaded relationship, it may be a relationship // that hasn't been loaded yet. If it is, we will lazy load it and // set the value of the relationship in the relationship array. diff --git a/laravel/database/eloquent/query.php b/laravel/database/eloquent/query.php index 1894fe5f..36cfaf6b 100644 --- a/laravel/database/eloquent/query.php +++ b/laravel/database/eloquent/query.php @@ -243,7 +243,7 @@ protected function query() * * @return Connection */ - protected function connection() + public function connection() { return Database::connection($this->model->connection()); } diff --git a/laravel/database/eloquent/relationships/belongs_to.php b/laravel/database/eloquent/relationships/belongs_to.php index 4ee38543..b73c57bf 100644 --- a/laravel/database/eloquent/relationships/belongs_to.php +++ b/laravel/database/eloquent/relationships/belongs_to.php @@ -15,11 +15,13 @@ public function results() /** * Update the parent model of the relationship. * - * @param array $attributes + * @param Model|array $attributes * @return int */ public function update($attributes) { + $attributes = ($attributes instanceof Model) ? $attributes->get_dirty() : $attributes; + return $this->model->update($this->foreign_value(), $attributes); } diff --git a/laravel/database/eloquent/relationships/has_many_and_belongs_to.php b/laravel/database/eloquent/relationships/has_many_and_belongs_to.php index 7f3c01b0..169e1f12 100644 --- a/laravel/database/eloquent/relationships/has_many_and_belongs_to.php +++ b/laravel/database/eloquent/relationships/has_many_and_belongs_to.php @@ -1,5 +1,6 @@ join_record($id), $attributes); @@ -88,12 +89,20 @@ public function add($id, $attributes = array()) /** * Insert a new record for the association. * - * @param array $attributes - * @param array $joining + * @param Model|array $attributes + * @param array $joining * @return bool */ public function insert($attributes, $joining = array()) { + // If the attributes are actually an instance of a model, we'll just grab the + // array of attributes off of the model for saving, allowing the developer + // to easily validate the joining models before inserting them. + if ($attributes instanceof Model) + { + $attributes = $attributes->attributes; + } + $model = $this->model->create($attributes); // If the insert was successful, we'll insert a record into the joining table @@ -138,9 +147,6 @@ protected function join_record($id) */ protected function insert_joining($attributes) { - // All joining tables get creation and update timestamps automatically even though - // some developers may not need them. This just provides them if necessary since - // it would be a pain for the developer to maintain them manually. $attributes['created_at'] = $this->model->get_timestamp(); $attributes['updated_at'] = $attributes['created_at']; diff --git a/laravel/database/eloquent/relationships/has_one_or_many.php b/laravel/database/eloquent/relationships/has_one_or_many.php index 3c9ad4a6..2cdac83e 100644 --- a/laravel/database/eloquent/relationships/has_one_or_many.php +++ b/laravel/database/eloquent/relationships/has_one_or_many.php @@ -7,11 +7,13 @@ class Has_One_Or_Many extends Relationship { /** * Insert a new record for the association. * - * @param array $attributes + * @param Model|array $attributes * @return bool */ public function insert($attributes) { + $attributes = ($attributes instanceof Model) ? $attributes->attributes : $attributes; + $attributes[$this->foreign_key()] = $this->base->get_key(); return $this->model->create($attributes); diff --git a/laravel/pluralizer.php b/laravel/pluralizer.php new file mode 100644 index 00000000..ace66fce --- /dev/null +++ b/laravel/pluralizer.php @@ -0,0 +1,130 @@ +config = $config; + } + + /** + * Get the singular form of the given word. + * + * @param string $value + * @return string + */ + public function singular($value) + { + // First we'll check the cache of inflected values. We cache each word that + // is inflected so we don't have to spin through the regular expressions + // each time we need to inflect a given value for the developer. + if (isset($this->singular[$value])) + { + return $this->singular[$value]; + } + + // English words may be automatically inflected using regular expressions. + // If the word is english, we'll just pass off the word to the automatic + // inflection method and return the result, which is cached. + $irregular = $this->config['irregular']; + + $result = $this->auto($value, $this->config['singular'], $irregular); + + return $this->singular[$value] = $result ?: $value; + } + + /** + * Get the plural form of the given word. + * + * @param string $value + * @param int $count + * @return string + */ + public function plural($value, $count = 2) + { + if ((int) $count == 1) return $value; + + // First we'll check the cache of inflected values. We cache each word that + // is inflected so we don't have to spin through the regular expressions + // each time we need to inflect a given value for the developer. + if (isset($this->plural[$value])) + { + return $this->plural[$value]; + } + + // English words may be automatically inflected using regular expressions. + // If the word is english, we'll just pass off the word to the automatic + // inflection method and return the result, which is cached. + $irregular = array_flip($this->config['irregular']); + + $result = $this->auto($value, $this->config['plural'], $irregular); + + return $this->plural[$value] = $result; + } + + /** + * Perform auto inflection on an English word. + * + * @param string $value + * @param array $source + * @param array $irregular + * @return string + */ + protected function auto($value, $source, $irregular) + { + // If the word hasn't been cached, we'll check the list of words that + // that are "uncountable". This should be a quick look up since we + // can just hit the array directly for the value. + if (in_array(strtolower($value), $this->config['uncountable'])) + { + return $value; + } + + // Next we will check the "irregular" patterns, which contains words + // like "children" and "teeth" which can not be inflected using the + // typically used regular expression matching approach. + foreach ($irregular as $irregular => $pattern) + { + if (preg_match($pattern = '/'.$pattern.'$/i', $value)) + { + return preg_replace($pattern, $irregular, $value); + } + } + + // Finally we'll spin through the array of regular expressions and + // and look for matches for the word. If we find a match we will + // cache and return the inflected value for quick look up. + foreach ($source as $pattern => $inflected) + { + if (preg_match($pattern, $value)) + { + return preg_replace($pattern, $inflected, $value); + } + } + } + +} \ No newline at end of file diff --git a/laravel/str.php b/laravel/str.php index 0b6b5cfa..78a554ed 100644 --- a/laravel/str.php +++ b/laravel/str.php @@ -2,6 +2,13 @@ class Str { + /** + * The pluralizer instance. + * + * @var Pluralizer + */ + public static $pluralizer; + /** * Get the default string encoding for the application. * @@ -154,25 +161,17 @@ public static function words($value, $words = 100, $end = '...') /** * Get the singular form of the given word. * - * The word should be defined in the "strings" configuration file. - * * @param string $value * @return string */ public static function singular($value) { - $inflection = Config::get('strings.inflection'); - - $singular = array_get(array_flip($inflection), strtolower($value), $value); - - return (ctype_upper($value[0])) ? static::title($singular) : $singular; + return static::pluralizer()->singular($value); } /** * Get the plural form of the given word. * - * The word should be defined in the "strings" configuration file. - * * * // Returns the plural form of "child" * $plural = Str::plural('child', 10); @@ -187,11 +186,19 @@ public static function singular($value) */ public static function plural($value, $count = 2) { - if ((int) $count == 1) return $value; + return static::pluralizer()->plural($value, $count); + } - $plural = array_get(Config::get('strings.inflection'), strtolower($value), $value); + /** + * Get the pluralizer instance. + * + * @return Pluralizer + */ + protected static function pluralizer() + { + $config = Config::get('strings'); - return (ctype_upper($value[0])) ? static::title($plural) : $plural; + return static::$pluralizer ?: static::$pluralizer = new Pluralizer($config); } /**