PHP Universal Feed Generator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
4.6 KiB

<?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();
}
}
}