1: <?php
2: /**
3: * This file is part of the FastFeed package.
4: *
5: * (c) Daniel González <daniel@desarrolla2.com>
6: *
7: * For the full copyright and license information, please view the LICENSE
8: * file that was distributed with this source code.
9: */
10:
11: namespace FastFeed;
12:
13: use Guzzle\Http\ClientInterface;
14: use Psr\Log\LoggerInterface;
15: use Psr\Log\LogLevel;
16: use FastFeed\Exception\LogicException;
17: use FastFeed\Parser\ParserInterface;
18: use FastFeed\Processor\ProcessorInterface;
19:
20: /**
21: * FastFeed
22: */
23: class FastFeed implements FastFeedInterface
24: {
25: /**
26: * @const VERSION
27: */
28: const VERSION = '0.1';
29:
30: /**
31: * @const USER_AGENT
32: */
33: const USER_AGENT = 'FastFeed/FastFeed';
34:
35: /**
36: * @var ClientInterface;
37: */
38: protected $http;
39:
40: /**
41: * @var LoggerInterface
42: */
43: protected $logger;
44:
45: /**
46: * @var array
47: */
48: protected $parsers = array();
49:
50: /**
51: * @var array
52: */
53: protected $processors = array();
54:
55: /**
56: * @var array
57: */
58: protected $feeds = array();
59:
60: /**
61: * @param ClientInterface $guzzle
62: * @param LoggerInterface $logger
63: */
64: public function __construct(ClientInterface $guzzle, LoggerInterface $logger)
65: {
66: $this->http = $guzzle;
67: $this->logger = $logger;
68: }
69:
70: /**
71: * Add feed to channel
72: *
73: * @param string $channel
74: * @param string $feed
75: *
76: * @throws LogicException
77: */
78: public function addFeed($channel, $feed)
79: {
80: if (!filter_var($feed, FILTER_VALIDATE_URL)) {
81: throw new LogicException('You tried to add a invalid url.');
82: }
83: $this->feeds[$channel][] = $feed;
84: }
85:
86: /**
87: * @param string $channel
88: *
89: * @return array
90: * @throws Exception\LogicException
91: */
92: public function fetch($channel = 'default')
93: {
94: if (!is_string($channel)) {
95: throw new LogicException('You tried to add a invalid channel.');
96: }
97:
98: $items = $this->retrieve($channel);
99:
100: foreach ($this->processors as $processor) {
101: $items = $processor->process($items);
102: }
103:
104: return $items;
105: }
106:
107: /**
108: * Retrieve a channel
109: *
110: * @param string $channel
111: *
112: * @return string
113: * @throws LogicException
114: */
115: public function getFeed($channel)
116: {
117: if (!isset($this->feeds[$channel])) {
118: throw new LogicException('You tried to get a not existent channel');
119: }
120:
121: return $this->feeds[$channel];
122: }
123:
124: /**
125: * @return ParserInterface
126: * @throws Exception\LogicException
127: */
128: public function popParser()
129: {
130: if (!$this->parsers) {
131: throw new LogicException('You tried to pop from an empty parsers stack.');
132: }
133:
134: return array_shift($this->parsers);
135: }
136:
137: /**
138: * @param ParserInterface $parser
139: */
140: public function pushParser(ParserInterface $parser)
141: {
142: $this->parsers[] = $parser;
143: }
144:
145: /**
146: * @return ProcessorInterface
147: * @throws Exception\LogicException
148: */
149: public function popProcessor()
150: {
151: if (!$this->processors) {
152: throw new LogicException('You tried to pop from an empty Processor stack.');
153: }
154:
155: return array_shift($this->processors);
156: }
157:
158: /**
159: * @param ProcessorInterface $processor
160: */
161: public function pushProcessor(ProcessorInterface $processor)
162: {
163: $this->processors[] = $processor;
164: }
165:
166: /**
167: * Retrieve all channels
168: *
169: * @return array
170: */
171: public function getFeeds()
172: {
173: return $this->feeds;
174: }
175:
176: /**
177: * Set Guzzle
178: *
179: * @param ClientInterface $guzzle
180: */
181: public function setHttpClient(ClientInterface $guzzle)
182: {
183: $this->http = $guzzle;
184: }
185:
186: /**
187: * @param LoggerInterface $logger
188: */
189: public function setLogger(LoggerInterface $logger)
190: {
191: $this->logger = $logger;
192: }
193:
194: /**
195: * Set a channel
196: *
197: * @param string $channel
198: * @param string $feed
199: *
200: * @throws LogicException
201: */
202: public function setFeed($channel, $feed)
203: {
204: if (!is_string($channel)) {
205: throw new LogicException('You tried to add a invalid channel.');
206: }
207: $this->feeds[$channel] = array();
208: $this->addFeed($channel, $feed);
209: }
210:
211: /**
212: * Retrieve content from a resource
213: *
214: * @param $url
215: *
216: * @return \Guzzle\Http\EntityBodyInterface|string
217: */
218: protected function get($url)
219: {
220: $request = $this->http->get(
221: $url,
222: array('User-Agent' => self::USER_AGENT . ' v.' . self::VERSION)
223: );
224:
225: $response = $request->send();
226:
227: if (!$response->isSuccessful()) {
228: $this->log('fail with ' . $response->getStatusCode() . ' http code in url "' . $url . '" ');
229:
230: return;
231: }
232: $this->logger->log(LogLevel::INFO, 'retrieved url "' . $url . '" ');
233:
234: return $response->getBody();
235: }
236:
237: /**
238: * @param $channel
239: *
240: * @return array
241: */
242: protected function retrieve($channel)
243: {
244: $result = array();
245:
246: foreach ($this->feeds[$channel] as $feed) {
247: $content = $this->get($feed);
248: if (!$content) {
249: continue;
250: }
251: $result = array_merge($result, $this->parse($content));
252: }
253:
254: return $result;
255: }
256:
257: /**
258: * @param $content
259: *
260: * @return array
261: */
262: protected function parse($content)
263: {
264: $result = array();
265: foreach ($this->parsers as $parser) {
266: $nodes = $parser->getNodes($content);
267: if (!$nodes) {
268: continue;
269: }
270:
271: foreach ($nodes as $node) {
272: $result[] = $node;
273: }
274: }
275:
276: return $result;
277: }
278:
279: /**
280: * @param $message
281: */
282: protected function log($message)
283: {
284: $this->logger->log(
285: LogLevel::INFO,
286: '[' . self::USER_AGENT . ' v.' . self::VERSION . '] - ' . $message
287: );
288: }
289: }
290: