From 14d8e1527e09968e4938794b3936b2aef7f96971 Mon Sep 17 00:00:00 2001 From: Nate Abele Date: Wed, 3 Feb 2010 11:50:52 -0500 Subject: [PATCH] Refactoring `util\Collection` to remove dependency on `http\Media`. Added `Collection::formats()` to enable registering format handler classes. --- app/config/bootstrap.php | 19 +++ libraries/lithium/g11n/Message.php | 4 +- libraries/lithium/net/http/Media.php | 30 +++++ .../tests/cases/net/http/MediaTest.php | 4 + .../tests/cases/util/CollectionTest.php | 1 + libraries/lithium/util/Collection.php | 120 ++++++++++++------ 6 files changed, 137 insertions(+), 41 deletions(-) diff --git a/app/config/bootstrap.php b/app/config/bootstrap.php index edd174b395..9231d31992 100644 --- a/app/config/bootstrap.php +++ b/app/config/bootstrap.php @@ -60,4 +60,23 @@ // 'default' => array('adapter' => 'Php') // )); +/** + * The `Collection` class, which serves as the base class for some of Lithium's data objects + * (`RecordSet` and `Document`) provides a way to manage data collections in a very flexible and + * intuitive way, using closures and SPL interfaces. The `to()` method allows a `Collection` (or + * subclass) to be converted to another format, such as an array. The `Collection` class also allows + * other classes to be connected as handlers to convert `Collection` objects to other formats. + * + * The following connects the `Media` class as a format handler, which allows `Collection`s to be + * exported to any format with a handler provided by `Media`, i.e. JSON. This enables things like + * the following: + * {{{ + * $posts = Post::find('all'); + * return $posts->to('json'); + * }}} + */ +use \lithium\util\Collection; + +Collection::formats('\lithium\http\Media'); + ?> \ No newline at end of file diff --git a/libraries/lithium/g11n/Message.php b/libraries/lithium/g11n/Message.php index 302179006d..7113337d68 100644 --- a/libraries/lithium/g11n/Message.php +++ b/libraries/lithium/g11n/Message.php @@ -82,9 +82,7 @@ public static function translate($id, $options = array()) { */ public static function contentFilters() { $t = function($message, $options = array()) { - return Message::translate($message, $options + array( - 'default' => $message - )); + return Message::translate($message, $options + array('default' => $message)); }; $tn = function($message1, $message2, $count, $options = array()) { return Message::translate($message1, $options + compact('count') + array( diff --git a/libraries/lithium/net/http/Media.php b/libraries/lithium/net/http/Media.php index 7d9618f5df..a1a85668f0 100644 --- a/libraries/lithium/net/http/Media.php +++ b/libraries/lithium/net/http/Media.php @@ -136,6 +136,36 @@ public static function types() { return array_keys(static::$_types); } + /** + * Alias for `types()`; included for interface compatibility with + * `lithium\util\Collection::to()`, which allows a collection object to be exported to any + * format supported by a `Media` handler. See the documentation for `Collection::to()` for more + * information. + * + * @see lithium\net\http\Media + * @return array Returns the value of `Media::types()`. + */ + public static function formats() { + return static::types(); + } + + /** + * Alias for `encode()`; included for interface compatibility with + * `lithium\util\Collection::to()`, which allows a collection object to be exported to any + * format supported by a `Media` handler. See the documentation for `Collection::to()` for more + * information. + * + * @param mixed $format Format into which data will be converted, i.e. `'json'`. + * @param mixed $data Either an array or object (usually an instance of `Collection`) which will + * be converted into the specified format. + * @param array $options Additional handler-specific options to pass to the content handler. + * @return mixed + */ + public static function to($format, $data, $options = array()) { + $data = is_object($data) ? $data->to('array') : $data; + return static::encode($format, $data, $options); + } + /** * Map an extension to a particular content-type (or types) with a set of options. * diff --git a/libraries/lithium/tests/cases/net/http/MediaTest.php b/libraries/lithium/tests/cases/net/http/MediaTest.php index b43ce3752c..f682135a32 100644 --- a/libraries/lithium/tests/cases/net/http/MediaTest.php +++ b/libraries/lithium/tests/cases/net/http/MediaTest.php @@ -27,6 +27,8 @@ public function testMediaTypes() { $this->assertTrue(in_array('json', $result)); $this->assertFalse(in_array('my', $result)); + $this->assertEqual($result, Media::formats()); + $result = Media::type('json'); $expected = 'application/json'; $this->assertEqual($expected, $result['content']); @@ -345,6 +347,8 @@ public function testMediaEncoding() { $result = Media::encode('json', $data); $this->assertEqual($expected, $result); + $this->assertEqual($result, Media::to('json', $data)); + $result = Media::encode('badness', $data); $this->assertNull($result); } diff --git a/libraries/lithium/tests/cases/util/CollectionTest.php b/libraries/lithium/tests/cases/util/CollectionTest.php index 8bef80e932..094f998ed7 100644 --- a/libraries/lithium/tests/cases/util/CollectionTest.php +++ b/libraries/lithium/tests/cases/util/CollectionTest.php @@ -219,6 +219,7 @@ public function testInternalKeys() { } public function testCollectionFormatConversion() { + Collection::formats('\lithium\http\Media'); $items = array('hello', 'goodbye', 'foo' => array('bar', 'baz' => 'dib')); $collection = new Collection(compact('items')); diff --git a/libraries/lithium/util/Collection.php b/libraries/lithium/util/Collection.php index b457488deb..40162613c1 100644 --- a/libraries/lithium/util/Collection.php +++ b/libraries/lithium/util/Collection.php @@ -14,6 +14,17 @@ */ class Collection extends \lithium\core\Object implements \ArrayAccess, \Iterator, \Countable { + /** + * A central registry of global format handlers for `Collection` objects and subclasses. + * Accessed via the `formats()` method. + * + * @see \lithium\util\Collection::formats() + * @var array + */ + protected static $_formats = array( + 'array' => '\lithium\util\Collection::_toArray' + ); + /** * The items contained in the collection. * @@ -34,19 +45,29 @@ class Collection extends \lithium\core\Object implements \ArrayAccess, \Iterator * * @var array */ - protected $_classes = array( - 'media' => '\lithium\net\http\Media' - ); + + protected $_autoConfig = array('items'); /** - * undocumented variable + * Accessor method for adding format handlers to instances and subclasses of `Collection`. * - * @var array + * @param string $format + * @param mixed $handler + * @return mixed */ - protected $_autoConfig = array('items'); + public static function formats($format, $handler = null) { + if ($format === false) { + return static::$_formats = array(); + } + if ((is_null($handler)) && class_exists($format)) { + return static::$_formats[] = $format; + } + return static::$_formats[$format] = $handler; + } /** - * undocumented function + * Initializes the collection object by merging in collection items and removing redundant + * object properties. * * @return void */ @@ -125,37 +146,30 @@ public function __call($method, $parameters = array()) { public function to($format, $options = array()) { $defaults = array('internal' => false); $options += $defaults; - $state = $options['internal'] ? $this->_items : $this; - $result = null; - - switch ($format) { - case 'array': - $result = array(); - - foreach ($state as $key => $value) { - if (is_object($value)) { - switch (true) { - case method_exists($value, 'to'): - $value = $value->to('array'); - break; - case (is_object($value) && $vars = get_object_vars($value)): - $value = $vars; - break; - case method_exists($value, '__toString'): - $value = $value->__toString(); - break; - } - } - $result[$key] = $value; - } - return $result; - default: - $media = $this->_classes['media']; - - if (in_array($format, $media::types())) { - return $media::encode($format, $this->to('array', $options)); - } - break; + $data = $options['internal'] ? $this->_items : $this; + + if (is_object($format) && is_callable($format)) { + return $format($data, $options); + } + + if (isset(static::$_formats[$format]) && is_callable(static::$_formats[$format])) { + $handler = static::$_formats[$format]; + $handler = is_string($handler) ? explode('::', $handler, 2) : $handler; + + if (is_array($handler)) { + list($class, $method) = $handler; + return $class::$method($data, $options); + } + return $handler($data, $options); + } + + foreach (static::$_formats as $key => $handler) { + if (!is_int($key)) { + continue; + } + if (in_array($format, $handler::formats($format, $data, $options))) { + return $handler::to($format, $data, $options); + } } } @@ -379,6 +393,36 @@ public function count() { public function keys() { return array_keys($this->_items); } + + /** + * Exports a `Collection` instance to an array. Used by `Collection::to()`. + * + * @param mixed $data Either a `Collection` instance, or an array representing a `Collection`'s + * internal state. + * @return array Returns the value of `$data` as a pure PHP array, recursively converting all + * sub-objects and other values to their closest array or scalar equivalents. + */ + protected static function _toArray($data) { + $result = array(); + + foreach ($data as $key => $item) { + switch (true) { + case (!is_object($item)): + $result[$key] = $item; + break; + case (method_exists($item, 'to')): + $result[$key] = $item->to('array'); + break; + case ($vars = get_object_vars($item)): + $result[$key] = $vars; + break; + case (method_exists($item, '__toString')): + $result[$key] = (string) $item; + break; + } + } + return $result; + } } ?> \ No newline at end of file