Skip to content

Commit

Permalink
Implement feed reader
Browse files Browse the repository at this point in the history
  • Loading branch information
jdecool committed May 29, 2017
1 parent d030a50 commit 30f4d3f
Show file tree
Hide file tree
Showing 8 changed files with 474 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
}
],
"require": {
"php": ">=5.4.0"
"php": ">=5.4.0",
"symfony/property-access": "^2.7|^3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.0|^6.1"
Expand Down
46 changes: 46 additions & 0 deletions src/Reader/Reader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace JDecool\JsonFeed\Reader;

use InvalidArgumentException;
use RuntimeException;

class Reader
{
/** @var ReaderInterface[] */
private $readers;

/**
* Constructor
*
* @param ReaderInterface[] $readers
*/
public function __construct(array $readers)
{
$this->readers = $readers;
}

/**
* Read feed from JSON
*
* @param string $json
* @return \JDecool\JsonFeed\Feed
*/
public function createFromJson($json)
{
$content = json_decode($json, true);
if (!is_array($content)) {
throw new InvalidArgumentException('Invalid JSONFeed string');
}

if (!isset($content['version'])) {
throw new RuntimeException('JSONFeed version is not defined');
}

if (!isset($this->readers[$content['version']])) {
throw new RuntimeException(sprintf('No reader for version "%s"', $content['version']));
}

return $this->readers[$content['version']]->readFromJson($json);
}
}
20 changes: 20 additions & 0 deletions src/Reader/ReaderBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace JDecool\JsonFeed\Reader;

use JDecool\JsonFeed\Versions;

class ReaderBuilder
{
/**
* Builder a reader
*
* @return Reader
*/
public function build()
{
return new Reader([
Versions::VERSION_1 => Version1\FeedReader::create(),
]);
}
}
16 changes: 16 additions & 0 deletions src/Reader/ReaderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace JDecool\JsonFeed\Reader;

use JDecool\JsonFeed\Feed;

interface ReaderInterface
{
/**
* Read feed from JSON
*
* @param string $json
* @return Feed
*/
public function readFromJson($json);
}
194 changes: 194 additions & 0 deletions src/Reader/Version1/FeedReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<?php

namespace JDecool\JsonFeed\Reader\Version1;

use DateTime;
use InvalidArgumentException;
use JDecool\JsonFeed\Attachment;
use JDecool\JsonFeed\Author;
use JDecool\JsonFeed\Feed;
use JDecool\JsonFeed\Hub;
use JDecool\JsonFeed\Item;
use JDecool\JsonFeed\Reader\ReaderInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;

class FeedReader implements ReaderInterface
{
/** @var \Symfony\Component\PropertyAccess\PropertyAccessor */
private $accessor;

/** @var FeedReader */
private static $instance;

/**
* Create reader instance
*
* @return FeedReader
*/
public static function create()
{
if (null === self::$instance) {
self::$instance = new self;
}

return self::$instance;
}

/**
* Constructor
*/
private function __construct()
{
$this->accessor = PropertyAccess::createPropertyAccessor();
}

/**
* {@inheritdoc}
*/
public function readFromJson($json)
{
$content = json_decode($json, true);
if (!is_array($content)) {
throw new InvalidArgumentException('Invalid JSONFeed string');
}

return $this->readFeedNode($content);
}

/**
* Browse feed node
*
* @param array $content
* @return Feed
*/
private function readFeedNode(array $content)
{
$feed = new Feed('');

foreach ($content as $key => $value) {
if ('version' === $key) {
continue;
}

switch ($key) {
case 'author':
$feed->setAuthor($this->readAuthorNode($value));
break;

case 'hubs':
$feed->setHubs(array_map([$this, 'readHubNode'], $value));
break;

case 'items':
$feed->setItems(array_map([$this, 'readItemNode'], $value));
break;

default:
$this->accessor->setValue($feed, $key, $value);
}
}

return $feed;
}

/**
* Browse item node
*
* @param array $content
* @return Item
*/
private function readItemNode(array $content)
{
$id = isset($content['id']) ? $content['id'] : '';

$item = new Item($id);
foreach ($content as $key => $value) {
if ('id' === $key) {
continue;
}

switch ($key) {
case 'attachments':
$item->setAttachments(array_map([$this, 'readAttachmentNode'], $value));
break;

case 'author':
$item->setAuthor($this->readAuthorNode($value));
break;

case 'date_published':
case 'date_modified':
$this->accessor->setValue($item, $key, new DateTime($value));
break;

default:
$this->accessor->setValue($item, $key, $value);
}
}

return $item;
}

/**
* Browse author node
*
* @param array $content
* @return Author
*/
private function readAuthorNode(array $content)
{
$name = (isset($content['name'])) ? $content['name'] : '';

$author = new Author($name);
foreach ($content as $key => $value) {
if ('name' === $key) {
continue;
}

$this->accessor->setValue($author, $key, $value);
}

return $author;
}

/**
* Browse hub node
*
* @param array $content
* @return Hub
*/
private function readHubNode(array $content)
{
$type = isset($content['type']) ? $content['type'] : '';
$url = isset($content['url']) ? $content['url'] : '';

return new Hub($type, $url);
}

/**
* Browse attachment node
*
* @param array $content
* @return Attachment
*/
private function readAttachmentNode(array $content)
{
$url = isset($content['url']) ? $content['url'] : '';
$mimeType = isset($content['mime_type']) ? $content['mime_type'] : '';

$attachment = new Attachment($url, $mimeType);
foreach ($content as $key => $value) {
switch ($key) {
case 'size_in_bytes':
$attachment->setSize($value);
break;

case 'duration_in_seconds':
$attachment->setDuration($value);
break;
}
}

return $attachment;
}
}
16 changes: 16 additions & 0 deletions test/Reader/ReaderBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace JDecool\Test\JsonFeed\Reader;

use JDecool\JsonFeed\Reader\ReaderBuilder;
use PHPUnit\Framework\TestCase;

class ReaderBuilderTest extends TestCase
{
public function testBuilder()
{
$builder = new ReaderBuilder();

$this->assertInstanceOf('JDecool\JsonFeed\Reader\Reader', $builder->build());
}
}
79 changes: 79 additions & 0 deletions test/Reader/ReaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace JDecool\Test\JsonFeed\Reader;

use JDecool\JsonFeed\Reader\Reader;
use PHPUnit\Framework\TestCase;

class ReaderTest extends TestCase
{
public function testCreateFromJson()
{
$json = <<<JSON
{
"version": "foo",
"title": "My Example Feed"
}
JSON;

$feedReader = $this->getMockBuilder('JDecool\JsonFeed\Reader\ReaderInterface')->getMock();
$feedReader->expects($this->once())
->method('readFromJson');

$reader = new Reader([
'foo' => $feedReader,
]);

$reader->createFromJson($json);
}

/**
* @expectedException RuntimeException
* @expectedExceptionMessage No reader for version "foo"
*/
public function testCreateFromJsonWithInvalidReader()
{
$json = <<<JSON
{
"version": "foo",
"title": "My Example Feed"
}
JSON;

$reader = new Reader([]);
$reader->createFromJson($json);
}

/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Invalid JSONFeed string
*/
public function testCreateFromJsonWithInvalidString()
{
$json = <<<JSON
{
"version": "foo",
"title": "My Example Feed",
}
JSON;

$reader = new Reader([]);
$reader->createFromJson($json);
}

/**
* @expectedException RuntimeException
* @expectedExceptionMessage JSONFeed version is not defined
*/
public function testCreateFromJsonWithoutVersion()
{
$json = <<<JSON
{
"title": "My Example Feed"
}
JSON;

$reader = new Reader([]);
$reader->createFromJson($json);
}
}
Loading

0 comments on commit 30f4d3f

Please sign in to comment.