From 50e05bdf1585550697b40364f7df25d7c2d7960f Mon Sep 17 00:00:00 2001 From: Sander Marechal Date: Thu, 3 Mar 2011 11:42:00 +0100 Subject: [PATCH] Added a jQuery-like interface for result sets This is a port of the ResultSet class from [DumbledORM][1]. The `find_many` method will return a ResultSet object instead of an array. All method calls on the result set will be passed onto all the models in the set. This allows for fancy jQuery-like syntax. [1]: https://github.com/jasonmoo/DumbledORM --- README.markdown | 26 +++++++++++++++++++++++++- paris.php | 28 ++++++++++++++++++++++++++-- test/test_queries.php | 9 +++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index a43d65db..6b20dfcb 100644 --- a/README.markdown +++ b/README.markdown @@ -21,6 +21,7 @@ Features * Built on top of [PDO](http://php.net/pdo). * Uses [prepared statements](http://uk.php.net/manual/en/pdo.prepared-statements.php) throughout to protect against [SQL injection](http://en.wikipedia.org/wiki/SQL_injection) attacks. * Database agnostic. Currently supports SQLite and MySQL. May support others, please give it a try! +* Supports jQuery-like syntax for working with collections of models Changelog --------- @@ -116,7 +117,9 @@ The only differences between using Idiorm and using Paris for querying are as fo 2. The `find_one` and `find_many` methods will return instances of *your model subclass*, instead of the base `ORM` class. Like Idiorm, `find_one` will return a single instance or `false` if no rows matched your query, while `find_many` will return an array of instances, which may be empty if no rows matched. -3. Custom filtering, see next section. +3. The `find_many` method will return an instance of ResultSet instead of an array. See the section on working with result sets. + +4. Custom filtering, see next section. You may also retrieve a count of the number of rows returned by your query. This method behaves exactly like Idiorm's `count` method: @@ -372,6 +375,27 @@ Despite this, Paris doesn't provide any built-in support for validation. This is However, there are several simple ways that you could add validation to your models without any help from Paris. You could override the `save()` method, check the data is valid, and return `false` on failure, or call `parent::save()` on success. You could create your own subclass of the `Model` base class and add your own generic validation methods. Or you could write your own external validation framework which you pass model instances to for checking. Choose whichever approach is most suitable for your own requirements. +### Working with result sets ### + +Paris makes it easy to work with collections of model instances, such as those returned by a `find_many` call. Unlike Idiorm, which returns an array of objects, Paris returns a `ResultSet` object. A `ResultSet` object can be used in any way that an array can be used with one additional feature: any method calls made on the `ResultSet` will be called on *all* the objects in the set. Aditionally, such calls will always return the `ResultSet` object itself, making for a fluent interface. This allows for a jQuery-like syntax when working with result sets: + + // Instead of this... + $people = Model::factory('Person')->find_many(); + foreach ($people as $person) { + $person->age = 50; + $person->save(); + } + + // ...you can do this + Model::factory('Person')->find_many() + ->set('age', 50) + ->save(); + + // Convenient mass-delete + Model::factory('Widget')->filter('expired')->find_many()->delete(); + +You can use this syntax with any method defined in your model. + ### Configuration ### The only configuration options provided by Paris itself are the `$_table` and `$_id_column` static properties on model classes. To configure the database connection, you should use Idiorm's configuration system via the `ORM::configure` method. **See [Idiorm's documentation](http://github.com/j4mie/idiorm/) for full details.** diff --git a/paris.php b/paris.php index 7d811af7..bb2a3ef9 100644 --- a/paris.php +++ b/paris.php @@ -117,11 +117,11 @@ public function find_one($id=null) { /** * Wrap Idiorm's find_many method to return - * an array of instances of the class associated + * a ResultSet of instances of the class associated * with this wrapper instead of the raw ORM class. */ public function find_many() { - return array_map(array($this, '_create_model_instance'), parent::find_many()); + return new ResultSet(array_map(array($this, '_create_model_instance'), parent::find_many())); } /** @@ -134,6 +134,30 @@ public function create($data=null) { } } + /** + * A simple class to work with collections of model instances + */ + class ResultSet extends ArrayIterator { + + /** + * Call a method on all children of a result set + * This allows fancy jQuery-like construct such as: + * + * Model::factory('Widget')->find_many() + * ->set('field', 'value') + * ->save(); + * + * Inspired by DumbledORM + * https://github.com/jasonmoo/DumbledORM/blob/master/dumbledorm.php + */ + public function __call($method, $params = array()) { + foreach ($this as $object) { + call_user_func_array(array($object, $method), $params); + } + return $this; + } + } + /** * Model base class. Your model objects should extend * this class. A minimal subclass would look like: diff --git a/test/test_queries.php b/test/test_queries.php index d6e7e96a..04fee26d 100644 --- a/test/test_queries.php +++ b/test/test_queries.php @@ -195,5 +195,14 @@ public function authors() { $expected = "SELECT `author_two`.* FROM `author_two` JOIN `wrote_the_book` ON `author_two`.`id` = `wrote_the_book`.`custom_author_id` WHERE `wrote_the_book`.`custom_book_id` = '1'"; Tester::check_equal("has_many_through relation with custom intermediate model and key names", $expected); + class ResultSetItem extends Model { + } + + $items = Model::factory('ResultSetItem')->find_many() + ->set('field', 'value') + ->save(); + $expected = "UPDATE `result_set_item` SET `field` = 'value' WHERE `id` = '1'"; + Tester::check_equal("Calling model methods on ResultSet", $expected); + Tester::report(); ?>