From 0455438ebe93a6744c446c3446fd845e106a5aef Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 18 Mar 2012 22:39:04 -0500 Subject: [PATCH 1/6] Added transaction method to database connection and eloquent model. --- laravel/database/connection.php | 27 +++++++++++++++++++++ laravel/database/eloquent/model.php | 37 ++++++++++++++++++----------- laravel/database/eloquent/query.php | 2 +- 3 files changed, 51 insertions(+), 15 deletions(-) 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 f1b7d74b..494a5064 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -103,17 +103,6 @@ public function __construct($attributes = array(), $exists = false) $this->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 +146,28 @@ 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; + } + + /** + * Execute a callback wrapped in a database transaction. + * + * @param Closure $callback + * @return void + */ + public static function transaction($callback) + { + with(new static)->query()->connection()->transaction($callback); + } + /** * Create a new model and store it in the database. * @@ -211,9 +222,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(); } /** 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()); } From d80730cf0de4e7e6b9de4d83fde0490c3387cee2 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 19 Mar 2012 08:43:07 -0500 Subject: [PATCH 2/6] Removed transaction method from Eloquent model since it is made pointless by DB::transaction. --- laravel/database/eloquent/model.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index 494a5064..c8522beb 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -157,17 +157,6 @@ public static function accessible($attributes) static::$accessible = $attributes; } - /** - * Execute a callback wrapped in a database transaction. - * - * @param Closure $callback - * @return void - */ - public static function transaction($callback) - { - with(new static)->query()->connection()->transaction($callback); - } - /** * Create a new model and store it in the database. * From 26afb000bfc82572cd2105643ced6ee45f52ca2d Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 19 Mar 2012 13:51:44 -0500 Subject: [PATCH 3/6] Added in automatic inflection on english strings. Non-english strings may be added to the "irregular" array. --- application/config/strings.php | 90 ++++++++++++++++++++--- laravel/pluralizer.php | 130 +++++++++++++++++++++++++++++++++ laravel/str.php | 31 +++++--- 3 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 laravel/pluralizer.php 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/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); } /** From e540fd3b6d39410ff27f614c51ed0c95236788da Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 19 Mar 2012 14:11:11 -0500 Subject: [PATCH 4/6] Automatically detect eloquent table names. --- laravel/database/eloquent/model.php | 11 +++++++++++ .../relationships/has_many_and_belongs_to.php | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index c8522beb..fc7b0380 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -1,5 +1,6 @@ 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. * 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..23b8765a 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 @@ model->get_timestamp(); $attributes['updated_at'] = $attributes['created_at']; From 079400ff3dfd41ad18bbfec6e93c116a8f16f473 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 20 Mar 2012 09:03:31 -0500 Subject: [PATCH 5/6] Allow passing in a model instance to relationship insert / update methods. --- .../eloquent/relationships/belongs_to.php | 4 +++- .../relationships/has_many_and_belongs_to.php | 17 +++++++++++------ .../eloquent/relationships/has_one_or_many.php | 4 +++- 3 files changed, 17 insertions(+), 8 deletions(-) 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 23b8765a..169e1f12 100644 --- a/laravel/database/eloquent/relationships/has_many_and_belongs_to.php +++ b/laravel/database/eloquent/relationships/has_many_and_belongs_to.php @@ -79,7 +79,7 @@ public function results() * @param array $joining * @return bool */ - public function add($id, $attributes = array()) + public function attach($id, $attributes = array()) { $joining = array_merge($this->join_record($id), $attributes); @@ -89,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 @@ -139,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 each 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); From 4336bb885a926efa124b8841bceeeb10fae42cce Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 20 Mar 2012 11:43:11 -0500 Subject: [PATCH 6/6] Fix attribute retrieval bug in Eq2. --- laravel/database/eloquent/model.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index fc7b0380..93c92bba 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -510,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.