| <?php | |
| namespace FeedWriter; | |
| 
 | |
| use \Iterator; | |
| use \Exception; | |
| use \stdClass; | |
| use \ArrayIterator; | |
| 
 | |
| class Feed { | |
| 
 | |
| 	const RSS1 = 'RSS 1.0'; | |
| 	const RSS2 = 'RSS 2.0'; | |
| 	const ATOM = 'ATOM'; | |
| 
 | |
|   protected $_iter  = null; | |
|   protected $_map   = []; | |
|   protected $_views = []; | |
| 
 | |
|   /** | |
|    * Generate one or more views of a collection from a given iterator or array. | |
|    * | |
|    * Takes a data source, an array describing a mapping from instances of the | |
|    * collection to items in the view, and a list of views to generate. | |
|    * | |
|    * This all sounds more abstract than it actually is, so let's run through | |
|    * some examples: | |
|    * | |
|    * <code> | |
|    * | |
|    * use \FeedWriter\Feed; | |
|    * use \FeedWriter\View\Atom; | |
|    * | |
|    * // Here's an array of arrays describing some blog posts.  These could also | |
|    * // be objects with properties like $blog_posts->writer. | |
|    * | |
|    * $blog_posts = [ | |
|    *   [ 'date_created' => '2013-02-14', 'writer' => 'Brennen Bearnes', 'text' => 'Hate.' ], | |
|    *   [ 'date_created' => '2013-07-04', 'writer' => 'Brennen Bearnes', 'text' => 'Explosions.' ], | |
|    *   [ 'date_created' => '2013-08-31', 'writer' => 'Brennen Bearnes', 'text' => 'A feed thingy.' ], | |
|    * ]; | |
|    * | |
|    * // Here's a map that explains how to find the properties we need for our | |
|    * // view: | |
|    * $entry_from_post = [ | |
|    *   'date'        => 'date_created', | |
|    *   'author'      => 'writer', | |
|    *   'content'     => 'text' | |
|    * ]; | |
|    * | |
|    * $feed = new Feed($blog_posts, $entry_from_post, ['atom' => new Atom]); | |
|    * | |
|    * // If everything went ok, views are now available as properties of the feed. | |
|    * if (! $feed->error()) | |
|    *   echo $feed->atom->render(); | |
|    * else | |
|    *   echo 'error: ' . $feed->error(); | |
|    * | |
|    * </code> | |
|    * | |
|    * @param $source mixed iterator or array | |
|    * @param $map array describing mapping of fields to fields | |
|    * @param $views array one or more views to generate | |
|    */ | |
|   public function __construct ($source, array $map, array $views) | |
|   { | |
|     if (is_array($source))  | |
|       $iter = new ArrayIterator($source); | |
|     else | |
|       $iter = $source; | |
| 
 | |
|     $this->setIter($iter); | |
|     $this->setViewsAndMap($views, $map); | |
|     $this->spin(); | |
|   } | |
| 
 | |
|   /** | |
|    * Explode if anybody tries to set a property directly. | |
|    */ | |
|   public function __set ($name, $view) | |
|   { | |
|     throw new Exception("You can't directly set properties on Feed. See documentation for constructor."); | |
|   } | |
| 
 | |
|   /** | |
|    * Get a view. | |
|    */ | |
|   public function __get ($name) | |
|   { | |
|     return $this->_views[$name]; | |
|   } | |
| 
 | |
|   /** | |
|    * Is a given view set? | |
|    */ | |
|   public function __isset ($name) | |
|   { | |
|     return isset($this->_views[$name]); | |
|   } | |
| 
 | |
|   /** | |
|    * Stash views and map, making sure that the map provides for anything | |
|    * the view explicitly requires. | |
|    */ | |
|   protected function setViewsAndMap (array $views, array $map) | |
|   { | |
|     if (! count($views)) | |
|       throw new Exception('Feed requires that at least one view instance be passed in)'); | |
| 
 | |
|     if (! is_array($map)) | |
|       throw new Exception('$map must be an array'); | |
| 
 | |
|     if (! count($map)) | |
|       throw new Exception('$map must provide values'); | |
| 
 | |
|     // Validate map for each view: | |
|     foreach ($views as $name => $view) { | |
|       if (! isset($view->require)) | |
|         continue; | |
| 
 | |
|       foreach ($view->require as $field) { | |
|         if (! isset($map[ $field ])) { | |
|           throw new Exception('$map should provide a mapping for ' . $field); | |
|         } | |
|       } | |
|     } | |
| 
 | |
|     $this->_views = $views; | |
|     $this->_map   = $map; | |
|   } | |
| 
 | |
|   protected function setIter (Iterator $iter) | |
|   { | |
|     if (! ($iter instanceof Iterator)) | |
|       throw new Exception('$iter must be an iterator'); | |
|     $this->_iter = $iter; | |
|   } | |
| 
 | |
|   /** | |
|    * Do the business of handing off values to views. | |
|    */ | |
|   protected function spin () | |
|   { | |
|     $output_item = (object)[]; | |
|     while ($this->_iter->valid()) { | |
|       $input_item = $this->_iter->current(); | |
| 
 | |
|       if (is_array($input_item)) | |
|         $input_item = (object)$input_item; | |
| 
 | |
|       foreach ($this->_map as $output_key => &$input_key) { | |
|         if (is_string($input_key)) | |
|         { | |
|           // shorthand for "use this field on the object for the value" | |
|           if (isset($input_item->$input_key)) | |
|             $output_item->$output_key = $input_item->$input_key; | |
|           else | |
|             throw new Exception("unable to access $input_key on input item: " . print_r($input_item, 1)); | |
|         } | |
|         elseif (is_callable($input_key)) | |
|         { | |
|           // if we have a function, pass it the item and expect it to | |
|           // return a value for our output key | |
|           $output_item->$output_key = $input_key($input_item); | |
|         } | |
|       } | |
| 
 | |
|       foreach ($this->_views as $name => $view) { | |
|         $view->collect($output_item); | |
|       } | |
| 
 | |
|       $this->_iter->next(); | |
|     } | |
|   } | |
| 
 | |
| }
 |