Skip to content
This repository was archived by the owner on Jan 31, 2025. It is now read-only.

Commit 30f4d3f

Browse files
committed
Implement feed reader
1 parent d030a50 commit 30f4d3f

File tree

8 files changed

+474
-1
lines changed

8 files changed

+474
-1
lines changed

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
}
1313
],
1414
"require": {
15-
"php": ">=5.4.0"
15+
"php": ">=5.4.0",
16+
"symfony/property-access": "^2.7|^3.0"
1617
},
1718
"require-dev": {
1819
"phpunit/phpunit": "^4.8|^5.0|^6.1"

src/Reader/Reader.php

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace JDecool\JsonFeed\Reader;
4+
5+
use InvalidArgumentException;
6+
use RuntimeException;
7+
8+
class Reader
9+
{
10+
/** @var ReaderInterface[] */
11+
private $readers;
12+
13+
/**
14+
* Constructor
15+
*
16+
* @param ReaderInterface[] $readers
17+
*/
18+
public function __construct(array $readers)
19+
{
20+
$this->readers = $readers;
21+
}
22+
23+
/**
24+
* Read feed from JSON
25+
*
26+
* @param string $json
27+
* @return \JDecool\JsonFeed\Feed
28+
*/
29+
public function createFromJson($json)
30+
{
31+
$content = json_decode($json, true);
32+
if (!is_array($content)) {
33+
throw new InvalidArgumentException('Invalid JSONFeed string');
34+
}
35+
36+
if (!isset($content['version'])) {
37+
throw new RuntimeException('JSONFeed version is not defined');
38+
}
39+
40+
if (!isset($this->readers[$content['version']])) {
41+
throw new RuntimeException(sprintf('No reader for version "%s"', $content['version']));
42+
}
43+
44+
return $this->readers[$content['version']]->readFromJson($json);
45+
}
46+
}

src/Reader/ReaderBuilder.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace JDecool\JsonFeed\Reader;
4+
5+
use JDecool\JsonFeed\Versions;
6+
7+
class ReaderBuilder
8+
{
9+
/**
10+
* Builder a reader
11+
*
12+
* @return Reader
13+
*/
14+
public function build()
15+
{
16+
return new Reader([
17+
Versions::VERSION_1 => Version1\FeedReader::create(),
18+
]);
19+
}
20+
}

src/Reader/ReaderInterface.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace JDecool\JsonFeed\Reader;
4+
5+
use JDecool\JsonFeed\Feed;
6+
7+
interface ReaderInterface
8+
{
9+
/**
10+
* Read feed from JSON
11+
*
12+
* @param string $json
13+
* @return Feed
14+
*/
15+
public function readFromJson($json);
16+
}

src/Reader/Version1/FeedReader.php

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?php
2+
3+
namespace JDecool\JsonFeed\Reader\Version1;
4+
5+
use DateTime;
6+
use InvalidArgumentException;
7+
use JDecool\JsonFeed\Attachment;
8+
use JDecool\JsonFeed\Author;
9+
use JDecool\JsonFeed\Feed;
10+
use JDecool\JsonFeed\Hub;
11+
use JDecool\JsonFeed\Item;
12+
use JDecool\JsonFeed\Reader\ReaderInterface;
13+
use Symfony\Component\PropertyAccess\PropertyAccess;
14+
15+
class FeedReader implements ReaderInterface
16+
{
17+
/** @var \Symfony\Component\PropertyAccess\PropertyAccessor */
18+
private $accessor;
19+
20+
/** @var FeedReader */
21+
private static $instance;
22+
23+
/**
24+
* Create reader instance
25+
*
26+
* @return FeedReader
27+
*/
28+
public static function create()
29+
{
30+
if (null === self::$instance) {
31+
self::$instance = new self;
32+
}
33+
34+
return self::$instance;
35+
}
36+
37+
/**
38+
* Constructor
39+
*/
40+
private function __construct()
41+
{
42+
$this->accessor = PropertyAccess::createPropertyAccessor();
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function readFromJson($json)
49+
{
50+
$content = json_decode($json, true);
51+
if (!is_array($content)) {
52+
throw new InvalidArgumentException('Invalid JSONFeed string');
53+
}
54+
55+
return $this->readFeedNode($content);
56+
}
57+
58+
/**
59+
* Browse feed node
60+
*
61+
* @param array $content
62+
* @return Feed
63+
*/
64+
private function readFeedNode(array $content)
65+
{
66+
$feed = new Feed('');
67+
68+
foreach ($content as $key => $value) {
69+
if ('version' === $key) {
70+
continue;
71+
}
72+
73+
switch ($key) {
74+
case 'author':
75+
$feed->setAuthor($this->readAuthorNode($value));
76+
break;
77+
78+
case 'hubs':
79+
$feed->setHubs(array_map([$this, 'readHubNode'], $value));
80+
break;
81+
82+
case 'items':
83+
$feed->setItems(array_map([$this, 'readItemNode'], $value));
84+
break;
85+
86+
default:
87+
$this->accessor->setValue($feed, $key, $value);
88+
}
89+
}
90+
91+
return $feed;
92+
}
93+
94+
/**
95+
* Browse item node
96+
*
97+
* @param array $content
98+
* @return Item
99+
*/
100+
private function readItemNode(array $content)
101+
{
102+
$id = isset($content['id']) ? $content['id'] : '';
103+
104+
$item = new Item($id);
105+
foreach ($content as $key => $value) {
106+
if ('id' === $key) {
107+
continue;
108+
}
109+
110+
switch ($key) {
111+
case 'attachments':
112+
$item->setAttachments(array_map([$this, 'readAttachmentNode'], $value));
113+
break;
114+
115+
case 'author':
116+
$item->setAuthor($this->readAuthorNode($value));
117+
break;
118+
119+
case 'date_published':
120+
case 'date_modified':
121+
$this->accessor->setValue($item, $key, new DateTime($value));
122+
break;
123+
124+
default:
125+
$this->accessor->setValue($item, $key, $value);
126+
}
127+
}
128+
129+
return $item;
130+
}
131+
132+
/**
133+
* Browse author node
134+
*
135+
* @param array $content
136+
* @return Author
137+
*/
138+
private function readAuthorNode(array $content)
139+
{
140+
$name = (isset($content['name'])) ? $content['name'] : '';
141+
142+
$author = new Author($name);
143+
foreach ($content as $key => $value) {
144+
if ('name' === $key) {
145+
continue;
146+
}
147+
148+
$this->accessor->setValue($author, $key, $value);
149+
}
150+
151+
return $author;
152+
}
153+
154+
/**
155+
* Browse hub node
156+
*
157+
* @param array $content
158+
* @return Hub
159+
*/
160+
private function readHubNode(array $content)
161+
{
162+
$type = isset($content['type']) ? $content['type'] : '';
163+
$url = isset($content['url']) ? $content['url'] : '';
164+
165+
return new Hub($type, $url);
166+
}
167+
168+
/**
169+
* Browse attachment node
170+
*
171+
* @param array $content
172+
* @return Attachment
173+
*/
174+
private function readAttachmentNode(array $content)
175+
{
176+
$url = isset($content['url']) ? $content['url'] : '';
177+
$mimeType = isset($content['mime_type']) ? $content['mime_type'] : '';
178+
179+
$attachment = new Attachment($url, $mimeType);
180+
foreach ($content as $key => $value) {
181+
switch ($key) {
182+
case 'size_in_bytes':
183+
$attachment->setSize($value);
184+
break;
185+
186+
case 'duration_in_seconds':
187+
$attachment->setDuration($value);
188+
break;
189+
}
190+
}
191+
192+
return $attachment;
193+
}
194+
}

test/Reader/ReaderBuilderTest.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace JDecool\Test\JsonFeed\Reader;
4+
5+
use JDecool\JsonFeed\Reader\ReaderBuilder;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class ReaderBuilderTest extends TestCase
9+
{
10+
public function testBuilder()
11+
{
12+
$builder = new ReaderBuilder();
13+
14+
$this->assertInstanceOf('JDecool\JsonFeed\Reader\Reader', $builder->build());
15+
}
16+
}

test/Reader/ReaderTest.php

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace JDecool\Test\JsonFeed\Reader;
4+
5+
use JDecool\JsonFeed\Reader\Reader;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class ReaderTest extends TestCase
9+
{
10+
public function testCreateFromJson()
11+
{
12+
$json = <<<JSON
13+
{
14+
"version": "foo",
15+
"title": "My Example Feed"
16+
}
17+
JSON;
18+
19+
$feedReader = $this->getMockBuilder('JDecool\JsonFeed\Reader\ReaderInterface')->getMock();
20+
$feedReader->expects($this->once())
21+
->method('readFromJson');
22+
23+
$reader = new Reader([
24+
'foo' => $feedReader,
25+
]);
26+
27+
$reader->createFromJson($json);
28+
}
29+
30+
/**
31+
* @expectedException RuntimeException
32+
* @expectedExceptionMessage No reader for version "foo"
33+
*/
34+
public function testCreateFromJsonWithInvalidReader()
35+
{
36+
$json = <<<JSON
37+
{
38+
"version": "foo",
39+
"title": "My Example Feed"
40+
}
41+
JSON;
42+
43+
$reader = new Reader([]);
44+
$reader->createFromJson($json);
45+
}
46+
47+
/**
48+
* @expectedException InvalidArgumentException
49+
* @expectedExceptionMessage Invalid JSONFeed string
50+
*/
51+
public function testCreateFromJsonWithInvalidString()
52+
{
53+
$json = <<<JSON
54+
{
55+
"version": "foo",
56+
"title": "My Example Feed",
57+
}
58+
JSON;
59+
60+
$reader = new Reader([]);
61+
$reader->createFromJson($json);
62+
}
63+
64+
/**
65+
* @expectedException RuntimeException
66+
* @expectedExceptionMessage JSONFeed version is not defined
67+
*/
68+
public function testCreateFromJsonWithoutVersion()
69+
{
70+
$json = <<<JSON
71+
{
72+
"title": "My Example Feed"
73+
}
74+
JSON;
75+
76+
$reader = new Reader([]);
77+
$reader->createFromJson($json);
78+
}
79+
}

0 commit comments

Comments
 (0)