diff --git a/README.md b/README.md index 2883f24..9037d3a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ docker run -p 8080:80 \ rezozero/mixedfeed ``` +or use `docker-compose`: copy `docker-compose.yml` to `docker-compose.test.yml` and fill your provider credentials in +it. Then execute `docker-compose -f docker-compose.test.yml up -d --force-recreate`, *Mixedfeed* will be available at +http://localhost:8080 + ### Available environment variables | Name | Default value | Multiple? (comma separated) | @@ -41,8 +45,6 @@ docker run -p 8080:80 \ | MF_FACEBOOK_ACCESS_TOKEN | | | | MF_FACEBOOK_FIELDS | from,link,picture,full_picture,message,story,type,created_time,source,status_type | ✅ | | MF_FACEBOOK_ENDPOINT | https://graph.facebook.com/v2.12/ | | -| MF_INSTAGRAM_USER_ID | | ✅ | -| MF_INSTAGRAM_ACCESS_TOKEN | | | | MF_GRAPH_INSTAGRAM_USER_ID | | ✅ | | MF_GRAPH_INSTAGRAM_ACCESS_TOKEN | | ✅ | | MF_GITHUB_RELEASES_REPOSITORY | | ✅ | @@ -145,7 +147,7 @@ return $feed->getAsyncCanonicalItems(12); ## Combine feeds -*mixedfeed* can combine multiple social feeds so you can loop over them and use some common data fields such as `feedItemPlatform`, `normalizedDate` and `canonicalMessage`. *mixedfeed* will sort all your feed items by *descending* `normalizedDate`, but you can configure it to sort *ascending*: +*mixedfeed* can combine multiple social feeds so you can loop over them and use some common data fields such as `feedItemPlatform`, `normalizedDate` and `canonicalMessage`. *mixedfeed* will sort all your feed items by *descending* `normalizedDate`, but you can configure it to sort *ascending*: ```php new MixedFeed([…], MixedFeed::ASC); @@ -181,11 +183,12 @@ object with essential data: `RZ\MixedFeed\Canonical\FeedItem`. *FeedItem* will p - likeCount `int|null` - shareCount `int|null`: Share, comments or retweet count depending on platform. - images `Image[]` - - url `string` - - width `integer` - - height `integer` + - url `string` + - width `integer` + - height `integer` - dateTime `DateTime` - tags `array` (only used with `MediumFeed`) +- raw `stdClass` to access raw API object if canonical item fields are not enough When FeedItem has images, `FeedItem::$images` will hold an array of `RZ\MixedFeed\Canonical\Image` objects to have better access to its `url`, `width` and `height` if they're available. @@ -221,12 +224,12 @@ There are plenty of APIs on the internet, and this tool won’t be able to handl But this is not a problem, you can easily create your own feed provider in *mixedfeed*. You just have to create a new *class* that will inherit from `RZ\MixedFeed\AbstractFeedProvider`. Then you will have to implement some methods from `FeedProviderInterface`: -* `getRequests($count = 5): \Generator` method which return a *Guzzle* `Request` generator to be transformed to a response. This is +* `getRequests($count = 5): \Generator` method which return a *Guzzle* `Request` generator to be transformed to a response. This is the best option as it will enable **async request pooling**. * `supportsRequestPool(): bool` method should return if your provider can be pooled to enhance performances. If you are using a third party library to fetch your data (such as some platform SDK), you should set it to `false`. * `createFeedItemFromObject($item)` method which transform a raw feed object into a canonical `RZ\MixedFeed\Canonical\FeedItem` and `RZ\MixedFeed\Canonical\Image` * `getDateTime` method to look for the critical datetime field in your feed. -* `getFeed` method to consume your API endpoint with a count limit and take care of caching your responses. +* `getFeed` method to consume your API endpoint with a count limit and take care of caching your responses. This method **must convert your own feed items into `\stdClass` objects, not arrays.** * `getCanonicalMessage` method to look for the important text content in your feed items. * `getFeedPlatform` method to get a global text identifier for your feed items. @@ -255,7 +258,7 @@ protected function getFeed($count = 5) $object = new \stdClass(); $object->native = $article; return $object; - }, + }, $this->entityManager->getRepository(Article::class)->findBy( [], ['datetime' => 'DESC'], @@ -270,13 +273,13 @@ protected function createFeedItemFromObject($item) $feedItem->setDateTime($this->getDateTime($item)); $feedItem->setMessage($this->getCanonicalMessage($item)); $feedItem->setPlatform($this->getFeedPlatform()); - + for ($item->images as $image) { $feedItemImage = new RZ\MixedFeed\Canonical\Image(); $feedItemImage->setUrl($image->url); $feedItem->addImage($feedItemImage); } - + return $feedItem; } ``` @@ -299,7 +302,7 @@ public function getDateTime($item) /** * @inheritDoc */ -public function getCanonicalMessage($item) +public function getCanonicalMessage(stdClass $item) { if ($item->native instanceof Article) { return $item->native->getExcerpt(); diff --git a/composer.json b/composer.json index 862f634..17cfd07 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,15 @@ { "name": "rezozero/mixedfeed", + "type": "library", "description": "A PHP library to get social networks feeds and merge them", + "keywords": [ + "feed", + "social", + "social networks", + "aggregator", + "rezo zero" + ], "license": "MIT", - "type": "library", "authors": [ { "name": "Ambroise Maupate", @@ -11,28 +18,29 @@ "role": "Lead developer" } ], - "keywords": [ - "feed", - "social", - "social networks", - "aggregator", - "rezo zero" - ], "require": { - "php": ">=7.2", - "abraham/twitteroauth": "^2.0.0", - "doctrine/cache": "^1.6.2", - "guzzlehttp/guzzle": "~6.0 || ~7.0", - "ext-json": "*" - }, - "autoload": { - "psr-4": {"RZ\\MixedFeed\\": "src/"} + "php": "^7.4 || ^8.0", + "ext-json": "*", + "abraham/twitteroauth": "^3.0", + "guzzlehttp/guzzle": "^7.0", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^2.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { - "squizlabs/php_codesniffer": "^3.3", "jms/serializer": "^2.1 || ^3.5", - "symfony/stopwatch": ">=4.2", - "symfony/dotenv": ">=4.2", - "phpstan/phpstan": "^0.12.14" + "phpstan/phpstan": "^1.0", + "squizlabs/php_codesniffer": "^3.3", + "symfony/cache": "^5.0", + "symfony/dotenv": "^5.0", + "symfony/stopwatch": "^5.0" + }, + "autoload": { + "psr-4": { + "RZ\\MixedFeed\\": "src/" + } + }, + "scripts": { + "analyze": "vendor/bin/phpstan" } } diff --git a/docker-compose.yml b/docker-compose.yml index 9e60327..133bdc4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,8 @@ services: # MF_FACEBOOK_ACCESS_TOKEN: "" # MF_FACEBOOK_FIELDS: "from,picture,full_picture,message,story,created_time,status_type,message_tags,shares,permalink_url" # MF_FACEBOOK_ENDPOINT: "https://graph.facebook.com/v2.12/" - # MF_INSTAGRAM_USER_ID: "" - # MF_INSTAGRAM_ACCESS_TOKEN: "" + # MF_GRAPH_INSTAGRAM_USER_ID: "" + # MF_GRAPH_INSTAGRAM_ACCESS_TOKEN: "" # MF_GITHUB_RELEASES_REPOSITORY: "" # MF_GITHUB_COMMITS_REPOSITORY: "" diff --git a/phpcs.xml.dist b/phpcs.xml.dist index af9a6a1..cea2e73 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -8,9 +8,10 @@ - + + src/ */node_modules diff --git a/phpstan.neon b/phpstan.neon index 12f3e15..4a91d60 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,12 +1,5 @@ parameters: - level: 3 + level: 8 + checkMissingIterableValueType: false paths: - src - excludes_analyse: - - */node_modules/* - - */bower_components/* - - */static/* - reportUnmatchedIgnoredErrors: false - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false - treatPhpDocTypesAsCertain: false diff --git a/src/AbstractFeedProvider.php b/src/AbstractFeedProvider.php index d82ec4c..9408874 100644 --- a/src/AbstractFeedProvider.php +++ b/src/AbstractFeedProvider.php @@ -1,187 +1,153 @@ cacheProvider = $cacheProvider; } - /** - * @inheritDoc - */ public function addError(string $reason): FeedProviderInterface { $this->errors[] = $reason; + return $this; } - /** - * @param int $count - * @return mixed - */ - protected function getFeed($count = 5) + public function isCacheHit(int $count = 5): bool { - $rawFeed = $this->getRawFeed($count); - if ($this->isValid($rawFeed)) { - return $rawFeed; + if (!$this->cacheProvider) { + return false; } - return []; - } - abstract protected function getCacheKey(): string; + $countKey = $this->getCacheKey() . $count; + + return $this->cacheProvider->getItem($countKey)->isHit(); + } /** - * @param int $count - * - * @return bool + * @param string $rawFeed */ - public function isCacheHit($count = 5): bool + public function setRawFeed(string $rawFeed): AbstractFeedProvider { - return null !== $this->cacheProvider && - $this->cacheProvider->contains($this->getCacheKey() . $count); + $this->rawFeed = Utils::jsonDecode($rawFeed); + + return $this; } /** - * @param string|array $rawFeed - * @param bool $json - * - * @return AbstractFeedProvider + * @return mixed */ - public function setRawFeed($rawFeed, $json = true): AbstractFeedProvider + protected function getFeed(int $count = 5) { - if ($json === true) { - $rawFeed = json_decode($rawFeed); - if ('No error' !== $jsonError = json_last_error_msg()) { - throw new \RuntimeException($jsonError); - } + $rawFeed = $this->getCachedRawFeed($count); + + if ($this->isValid($rawFeed)) { + return $rawFeed; } - $this->rawFeed = $rawFeed; - return $this; + + return []; } /** - * @param int $count - * * @return mixed + * * @throws FeedProviderErrorException */ - protected function getRawFeed($count = 5) + protected function getRawFeed(int $count = 5) { - $countKey = $this->getCacheKey() . $count; - if (null !== $this->rawFeed) { - if (null !== $this->cacheProvider && - !$this->cacheProvider->contains($countKey)) { - $this->cacheProvider->save( - $countKey, - $this->rawFeed, - $this->ttl - ); - } return $this->rawFeed; } try { - if (null !== $this->cacheProvider && - $this->cacheProvider->contains($countKey)) { - return $this->cacheProvider->fetch($countKey); - } - $client = new Client([ 'http_errors' => true, ]); $response = $client->send($this->getRequests($count)->current()); - $body = json_decode($response->getBody()->getContents()); - if ('No error' !== $jsonError = json_last_error_msg()) { - throw new FeedProviderErrorException($this->getFeedPlatform(), $jsonError); - } - if (null !== $this->cacheProvider) { - $this->cacheProvider->save( - $countKey, - $body, - $this->ttl - ); - } - return $body; + return Utils::jsonDecode($response->getBody()->getContents()); } catch (GuzzleException $e) { throw new FeedProviderErrorException($this->getFeedPlatform(), $e->getMessage(), $e); } } /** - * {@inheritdoc} + * @return mixed + * + * @throws FeedProviderErrorException */ - public function getItems($count = 5) + protected function getCachedRawFeed(int $count = 5) { - $list = $this->getFeed($count); - /* - * Need to inject feed item platform, normalizedDate and canonicalMessage - * to be able to merge them with other types - */ - foreach ($list as $index => $item) { - if (is_object($item)) { - $item->feedItemPlatform = $this->getFeedPlatform(); - $item->normalizedDate = $this->getDateTime($item); - $item->canonicalMessage = $this->getCanonicalMessage($item); - } else { - unset($list[$index]); - } + if (!$this->cacheProvider) { + return $this->getRawFeed($count); + } + + $countKey = $this->getCacheKey() . $count; + $item = $this->cacheProvider->getItem($countKey); + + if ($item->isHit()) { + return $item->get(); } - return $list; + + $feed = $this->getRawFeed($count); + $item->set($feed); + $item->expiresAfter($this->ttl); + + $this->cacheProvider->save($item); + + return $feed; } /** * {@inheritdoc} */ - public function getCanonicalItems($count = 5) + public function getCanonicalItems(int $count = 5): array { $items = []; - foreach ($this->getFeed($count) as $index => $item) { - if (is_object($item)) { + + foreach ($this->getFeed($count) as $item) { + if ($item instanceof stdClass) { $items[] = $this->createFeedItemFromObject($item); } } + return $items; } /** * Gets the value of ttl. - * - * @return integer */ - public function getTtl() + public function getTtl(): ?int { return $this->ttl; } @@ -189,34 +155,26 @@ public function getTtl() /** * Sets the value of ttl. * - * @param integer $ttl the ttl - * - * @return self + * @param int $ttl the ttl */ - public function setTtl($ttl) + public function setTtl(?int $ttl): AbstractFeedProvider { $this->ttl = $ttl; return $this; } - /** - * @param \stdClass $item - * - * @return FeedItem - */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = new FeedItem(); + $feedItem->setRaw($item); $feedItem->setDateTime($this->getDateTime($item)); $feedItem->setMessage($this->getCanonicalMessage($item)); $feedItem->setPlatform($this->getFeedPlatform()); + return $feedItem; } - /** - * @inheritDoc - */ public function supportsRequestPool(): bool { return true; @@ -225,24 +183,12 @@ public function supportsRequestPool(): bool /** * {@inheritdoc} */ - public function isValid($feed) + public function isValid($feed): bool { - if (count($this->errors) > 0) { - throw new FeedProviderErrorException($this->getFeedPlatform(), implode(', ', $this->errors)); + if (\count($this->errors) > 0) { + throw new FeedProviderErrorException($this->getFeedPlatform(), \implode(', ', $this->errors)); } - return null !== $feed && is_iterable($feed->items); - } - /** - * Get errors details. - * - * @param mixed $feed - * - * @return string - * @deprecated Catch FeedProviderErrorException for errors details. - */ - public function getErrors($feed) - { - return $feed['error']; + return null !== $feed && \is_iterable($feed->items); } } diff --git a/src/AbstractFeedProvider/AbstractTwitterFeed.php b/src/AbstractFeedProvider/AbstractTwitterFeed.php index f46fa99..8ab395f 100644 --- a/src/AbstractFeedProvider/AbstractTwitterFeed.php +++ b/src/AbstractFeedProvider/AbstractTwitterFeed.php @@ -1,13 +1,17 @@ accessToken = $accessToken; - if (null === $accessToken || - false === $accessToken || - empty($accessToken)) { - throw new CredentialsException("TwitterSearchFeed needs a valid access token.", 1); + if (empty($accessToken)) { + throw new CredentialsException('TwitterSearchFeed needs a valid access token.', 1); } - if (null === $accessTokenSecret || - false === $accessTokenSecret || - empty($accessTokenSecret)) { - throw new CredentialsException("TwitterSearchFeed needs a valid access token secret.", 1); + if (empty($accessTokenSecret)) { + throw new CredentialsException('TwitterSearchFeed needs a valid access token secret.', 1); } - if (null === $consumerKey || - false === $consumerKey || - empty($consumerKey)) { - throw new CredentialsException("TwitterSearchFeed needs a valid consumer key.", 1); + if (empty($consumerKey)) { + throw new CredentialsException('TwitterSearchFeed needs a valid consumer key.', 1); } - if (null === $consumerSecret || - false === $consumerSecret || - empty($consumerSecret)) { - throw new CredentialsException("TwitterSearchFeed needs a valid consumer secret.", 1); + if (empty($consumerSecret)) { + throw new CredentialsException('TwitterSearchFeed needs a valid consumer secret.', 1); } $this->twitterConnection = new TwitterOAuth( @@ -82,17 +64,15 @@ public function __construct( /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): DateTime { - $date = new \DateTime(); - $date->setTimestamp(strtotime($item->created_at)); - return $date; + return new DateTime('@' . \strtotime($item->created_at)); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { if (isset($item->text)) { return $item->text; @@ -101,12 +81,7 @@ public function getCanonicalMessage($item) return $item->full_text; } - /** - * @param int $count - * - * @return \Generator - */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { throw new \RuntimeException('Twitter cannot be used in async mode'); } @@ -114,7 +89,7 @@ public function getRequests($count = 5): \Generator /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'twitter'; } @@ -122,35 +97,19 @@ public function getFeedPlatform() /** * {@inheritdoc} */ - public function isValid($feed) - { - if (count($this->errors) > 0) { - throw new FeedProviderErrorException($this->getFeedPlatform(), implode(', ', $this->errors)); - } - return null !== $feed && is_array($feed); - } - - /** - * {@inheritdoc} - */ - public function getErrors($feed) + public function isValid($feed): bool { - $errors = ""; - - if (null !== $feed && null !== $feed->errors && !empty($feed->errors)) { - foreach ($feed->errors as $error) { - $errors .= "[" . $error->code . "] "; - $errors .= $error->message . PHP_EOL; - } + if (\count($this->errors) > 0) { + throw new FeedProviderErrorException($this->getFeedPlatform(), \implode(', ', $this->errors)); } - return $errors; + return null !== $feed && \is_array($feed); } /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->id_str); @@ -168,8 +127,8 @@ protected function createFeedItemFromObject($item): FeedItem if (isset($item->entities->hashtags)) { foreach ($item->entities->hashtags as $hashtag) { - $feedItem->setTags(array_merge($feedItem->getTags(), [ - $hashtag->text + $feedItem->setTags(\array_merge($feedItem->getTags(), [ + $hashtag->text, ])); } } diff --git a/src/AbstractFeedProvider/AbstractYoutubeVideoFeed.php b/src/AbstractFeedProvider/AbstractYoutubeVideoFeed.php index 1d6f64b..fc459e6 100644 --- a/src/AbstractFeedProvider/AbstractYoutubeVideoFeed.php +++ b/src/AbstractFeedProvider/AbstractYoutubeVideoFeed.php @@ -1,80 +1,80 @@ apiKey = $apiKey; - if (null === $this->apiKey || - false === $this->apiKey || - empty($this->apiKey)) { - throw new CredentialsException("YoutubeVideoFeed needs a valid apiKey.", 1); + if (empty($this->apiKey)) { + throw new CredentialsException('YoutubeVideoFeed needs a valid apiKey.', 1); } } - protected function getFeed($count = 5) + protected function getFeed(int $count = 5) { - $rawFeed = $this->getRawFeed($count); + $rawFeed = $this->getCachedRawFeed($count); if ($this->isValid($rawFeed)) { return $rawFeed->items; } + return []; } /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): ?DateTime { if (isset($item->snippet->publishedAt)) { - return new \DateTime($item->snippet->publishedAt); + return new DateTime($item->snippet->publishedAt); } + return null; } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { if (isset($item->snippet->title)) { return $item->snippet->title; } - return ""; + return ''; } /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->id); - $feedItem->setLink('https://www.youtube.com/watch?v=' . $item->id); + $feedItem->setLink(\sprintf(self::YOUTUBE_URL_FORMAT, $item->id)); $feedItem->setTitle($item->snippet->title); $feedItem->setMessage($item->snippet->description); $feedItem->setAuthor($item->snippet->channelTitle); @@ -90,6 +90,7 @@ protected function createFeedItemFromObject($item): FeedItem $feedItemImage->setHeight($item->snippet->thumbnails->maxres->height); $feedItem->addImage($feedItemImage); } + return $feedItem; } } diff --git a/src/Canonical/FeedItem.php b/src/Canonical/FeedItem.php index e863d1d..74653a0 100644 --- a/src/Canonical/FeedItem.php +++ b/src/Canonical/FeedItem.php @@ -1,281 +1,192 @@ id; } - /** - * @param string $id - * - * @return FeedItem - */ - public function setId($id) + public function setId(string $id): FeedItem { $this->id = $id; return $this; } - /** - * @return string - */ - public function getPlatform() + public function getRaw(): ?stdClass + { + return $this->raw; + } + + public function setRaw(stdClass $object): FeedItem + { + $this->raw = $object; + + return $this; + } + + public function getPlatform(): string { return $this->platform; } - /** - * @param string $platform - * - * @return FeedItem - */ - public function setPlatform($platform) + public function setPlatform(string $platform): FeedItem { $this->platform = $platform; return $this; } - /** - * @return string - */ - public function getAuthor() + public function getAuthor(): string { return $this->author; } - /** - * @param string $author - * - * @return FeedItem - */ - public function setAuthor($author) + public function setAuthor(string $author): FeedItem { $this->author = $author; return $this; } - /** - * @return string - */ - public function getTitle() + public function getTitle(): string { return $this->title; } - /** - * @param string $title - * - * @return FeedItem - */ - public function setTitle($title) + public function setTitle(string $title): FeedItem { $this->title = $title; return $this; } - /** - * @return Image[] - */ - public function getImages() + /** @return Image[] */ + public function getImages(): array { return $this->images; } - /** - * @param Image[] $images - * - * @return FeedItem - */ - public function setImages($images) + /** @param Image[] $images */ + public function setImages(array $images): FeedItem { $this->images = $images; return $this; } - /** - * @param Image $image - * - * @return FeedItem - */ - public function addImage(Image $image) + public function addImage(Image $image): FeedItem { $this->images[] = $image; + return $this; } - /** - * @return string - */ - public function getMessage() + public function getMessage(): string { return $this->message; } - /** - * @param string $message - * - * @return FeedItem - */ - public function setMessage($message) + public function setMessage(string $message): FeedItem { $this->message = $message; return $this; } - /** - * @return \DateTime - */ - public function getDateTime() + public function getDateTime(): ?DateTime { return $this->dateTime; } - /** - * @param \DateTime $dateTime - * - * @return FeedItem - */ - public function setDateTime($dateTime) + public function setDateTime(?DateTime $dateTime): FeedItem { $this->dateTime = $dateTime; return $this; } - /** - * @return string - */ - public function getLink() + public function getLink(): string { return $this->link; } - /** - * @param string $link - * - * @return FeedItem - */ - public function setLink($link) + public function setLink(string $link): FeedItem { $this->link = $link; return $this; } - /** - * @return array - */ - public function getTags() + /** @return string[] */ + public function getTags(): array { return $this->tags; } - /** - * @param array $tags - * - * @return FeedItem - */ - public function setTags(array $tags) + /** @param string[] $tags */ + public function setTags(array $tags): FeedItem { $this->tags = $tags; return $this; } - /** - * @return int|null - */ public function getLikeCount(): ?int { return $this->likeCount; } - /** - * @param int $likeCount - * - * @return FeedItem - */ - public function setLikeCount(int $likeCount) + public function setLikeCount(int $likeCount): FeedItem { $this->likeCount = $likeCount; return $this; } - /** - * @return int|null - */ public function getShareCount(): ?int { return $this->shareCount; } - /** - * @param int $shareCount - * - * @return FeedItem - */ - public function setShareCount(int $shareCount) + public function setShareCount(int $shareCount): FeedItem { $this->shareCount = $shareCount; diff --git a/src/Canonical/Image.php b/src/Canonical/Image.php index 21d546d..3174aac 100644 --- a/src/Canonical/Image.php +++ b/src/Canonical/Image.php @@ -1,75 +1,45 @@ url; } - /** - * @param string $url - * - * @return Image - */ - public function setUrl($url) + public function setUrl(string $url): Image { $this->url = $url; return $this; } - /** - * @return int - */ - public function getWidth() + public function getWidth(): int { return $this->width; } - /** - * @param int $width - * - * @return Image - */ - public function setWidth($width) + public function setWidth(int $width): Image { $this->width = $width; return $this; } - /** - * @return int - */ - public function getHeight() + public function getHeight(): int { return $this->height; } - /** - * @param int $height - * - * @return Image - */ - public function setHeight($height) + public function setHeight(int $height): Image { $this->height = $height; diff --git a/src/Env/CacheResolver.php b/src/Env/CacheResolver.php index 8b98ea9..94de636 100644 --- a/src/Env/CacheResolver.php +++ b/src/Env/CacheResolver.php @@ -1,29 +1,27 @@ * - * @return FeedProviderInterface[] - * @throws \RZ\MixedFeed\Exception\CredentialsException + * @throws CredentialsException */ - public static function parseFromEnvironment(CacheProvider $cache = null) + public static function parseFromEnvironment(?CacheItemPoolInterface $cache = null): array { $feedProviders = []; - if (false !== $facebookPageIds = getenv('MF_FACEBOOK_PAGE_ID')) { - $facebookPageIds = explode(',', $facebookPageIds); + + if (false !== $facebookPageIds = $_ENV['MF_FACEBOOK_PAGE_ID'] ?? false) { + $facebookPageIds = \explode(',', $facebookPageIds); + foreach ($facebookPageIds as $facebookPageId) { $facebookProvider = new FacebookPageFeed( $facebookPageId, - getenv('MF_FACEBOOK_ACCESS_TOKEN'), + $_ENV['MF_FACEBOOK_ACCESS_TOKEN'] ?? '', $cache, - getenv('MF_FACEBOOK_FIELDS') ? - explode(',', getenv('MF_FACEBOOK_FIELDS')): + isset($_ENV['MF_FACEBOOK_FIELDS']) ? + \explode(',', $_ENV['MF_FACEBOOK_FIELDS']) : [], - getenv('MF_FACEBOOK_ENDPOINT') + $_ENV['MF_FACEBOOK_ENDPOINT'] ?? null ); - array_push($feedProviders, $facebookProvider); + \array_push($feedProviders, $facebookProvider); } } + /* * Youtube playlist */ - if (false !== $youtubePlaylistIds = getenv('MF_YOUTUBE_PLAYLIST_ID')) { - $youtubePlaylistIds = explode(',', $youtubePlaylistIds); + if (false !== $youtubePlaylistIds = \getenv('MF_YOUTUBE_PLAYLIST_ID')) { + $youtubePlaylistIds = \explode(',', $youtubePlaylistIds); + foreach ($youtubePlaylistIds as $youtubePlaylistId) { $youtubePlaylistProvider = new YoutubePlaylistItemFeed( $youtubePlaylistId, - getenv('MF_YOUTUBE_API_KEY'), + $_ENV['MF_YOUTUBE_API_KEY'] ?? '', $cache ); - array_push($feedProviders, $youtubePlaylistProvider); + \array_push($feedProviders, $youtubePlaylistProvider); } } + /* * Former Instagram API */ - if (false !== $instagramUserIds = getenv('MF_INSTAGRAM_USER_ID')) { - $instagramUserIds = explode(',', $instagramUserIds); + if (isset($_ENV['MF_INSTAGRAM_USER_ID'])) { + $instagramUserIds = \explode(',', $_ENV['MF_INSTAGRAM_USER_ID']); + foreach ($instagramUserIds as $instagramUserId) { $instagramProvider = new InstagramFeed( $instagramUserId, - getenv('MF_INSTAGRAM_ACCESS_TOKEN'), + $_ENV['MF_INSTAGRAM_ACCESS_TOKEN'] ?? '', $cache ); - array_push($feedProviders, $instagramProvider); + \array_push($feedProviders, $instagramProvider); } } + /* * Graph instagram */ - $instagramUserIds = getenv('MF_GRAPH_INSTAGRAM_USER_ID'); - $instagramAccessTokens = getenv('MF_GRAPH_INSTAGRAM_ACCESS_TOKEN'); - if (false !== $instagramUserIds && false !== $instagramAccessTokens) { - $instagramUserIds = explode(',', $instagramUserIds); - $instagramAccessTokens = explode(',', $instagramAccessTokens); + $instagramUserIds = $_ENV['MF_GRAPH_INSTAGRAM_USER_ID'] ?? null; + $instagramAccessTokens = $_ENV['MF_GRAPH_INSTAGRAM_ACCESS_TOKEN'] ?? null; + + if ($instagramUserIds && $instagramAccessTokens) { + $instagramUserIds = \explode(',', $instagramUserIds); + $instagramAccessTokens = \explode(',', $instagramAccessTokens); + foreach ($instagramUserIds as $i => $instagramUserId) { - if (isset($instagramAccessTokens[$i])) { - $instagramProvider = new GraphInstagramFeed( - $instagramUserId, - $instagramAccessTokens[$i], - $cache - ); - array_push($feedProviders, $instagramProvider); + if (!isset($instagramAccessTokens[$i])) { + continue; } + + $instagramProvider = new GraphInstagramFeed($instagramUserId, $instagramAccessTokens[$i], $cache); + \array_push($feedProviders, $instagramProvider); } } - if (false !== $instagramOEmbedId = getenv('MF_INSTAGRAM_OEMBED_ID')) { + + if (isset($_ENV['MF_INSTAGRAM_OEMBED_ID'])) { $instagramOEmbedProvider = new InstagramOEmbedFeed( - explode(',', $instagramOEmbedId), - $cache + \explode(',', $_ENV['MF_INSTAGRAM_OEMBED_ID']), + $cache, ); - array_push($feedProviders, $instagramOEmbedProvider); + \array_push($feedProviders, $instagramOEmbedProvider); } - if (false !== $mediumUserNames = getenv('MF_MEDIUM_USERNAME')) { - $mediumUserNames = explode(',', $mediumUserNames); - if (false !== $mediumUserIds = getenv('MF_MEDIUM_USER_ID')) { - $mediumUserIds = explode(',', $mediumUserIds); - } else { - $mediumUserIds = []; - } + + if (isset($_ENV['MF_MEDIUM_USERNAME'])) { + $mediumUserNames = \explode(',', $_ENV['MF_MEDIUM_USERNAME']); + + $mediumUserIds = $_ENV['MF_MEDIUM_USER_ID']; + $mediumUserIds = \explode(',', $mediumUserIds); foreach ($mediumUserNames as $i => $mediumUserName) { $mediumUserId = null; + if (!empty($mediumUserIds[$i])) { $mediumUserId = $mediumUserIds[$i]; } - $mediumProvider = new MediumFeed( - $mediumUserName, - $cache, - $mediumUserId - ); - array_push($feedProviders, $mediumProvider); + + $mediumProvider = new MediumFeed($mediumUserName, $cache, $mediumUserId); + \array_push($feedProviders, $mediumProvider); } } - if (false !== $pinterestBoardIds = getenv('MF_PINTEREST_BOARD_ID')) { - $pinterestBoardIds = explode(',', $pinterestBoardIds); + + if (isset($_ENV['MF_PINTEREST_BOARD_ID'])) { + $pinterestBoardIds = \explode(',', $_ENV['MF_PINTEREST_BOARD_ID']); + foreach ($pinterestBoardIds as $pinterestBoardId) { $pinterestProvider = new PinterestBoardFeed( $pinterestBoardId, - getenv('MF_PINTEREST_ACCESS_TOKEN'), + $_ENV['MF_PINTEREST_ACCESS_TOKEN'] ?? '', $cache ); - array_push($feedProviders, $pinterestProvider); + \array_push($feedProviders, $pinterestProvider); } } - if (false !== $githubReleasesRepos = getenv('MF_GITHUB_RELEASES_REPOSITORY')) { - $githubReleasesRepos = explode(',', $githubReleasesRepos); + + if (isset($_ENV['MF_GITHUB_RELEASES_REPOSITORY'])) { + $githubReleasesRepos = \explode(',', $_ENV['MF_GITHUB_RELEASES_REPOSITORY']); + foreach ($githubReleasesRepos as $githubReleasesRepo) { $githubReleasesProvider = new GithubReleasesFeed( $githubReleasesRepo, - getenv('MF_GITHUB_ACCESS_TOKEN'), + $_ENV['MF_GITHUB_ACCESS_TOKEN'] ?? '', $cache ); - array_push($feedProviders, $githubReleasesProvider); + \array_push($feedProviders, $githubReleasesProvider); } } - if (false !== $githubCommitsRepos = getenv('MF_GITHUB_COMMITS_REPOSITORY')) { - $githubCommitsRepos = explode(',', $githubCommitsRepos); + + if (isset($_ENV['MF_GITHUB_COMMITS_REPOSITORY'])) { + $githubCommitsRepos = \explode(',', $_ENV['MF_GITHUB_COMMITS_REPOSITORY']); + foreach ($githubCommitsRepos as $githubCommitsRepo) { $githubCommitsProvider = new GithubCommitsFeed( $githubCommitsRepo, - getenv('MF_GITHUB_ACCESS_TOKEN'), + $_ENV['MF_GITHUB_ACCESS_TOKEN'] ?? '', $cache ); - array_push($feedProviders, $githubCommitsProvider); + \array_push($feedProviders, $githubCommitsProvider); } } - if (false !== $twitterUserIds = getenv('MF_TWITTER_USER_ID')) { - $twitterUserIds = explode(',', $twitterUserIds); + + if (isset($_ENV['MF_TWITTER_USER_ID'])) { + $twitterUserIds = \explode(',', $_ENV['MF_TWITTER_USER_ID']); + foreach ($twitterUserIds as $twitterUserId) { $twitterProvider = new TwitterFeed( $twitterUserId, - getenv('MF_TWITTER_CONSUMER_KEY'), - getenv('MF_TWITTER_CONSUMER_SECRET'), - getenv('MF_TWITTER_ACCESS_TOKEN'), - getenv('MF_TWITTER_ACCESS_TOKEN_SECRET'), + $_ENV['MF_TWITTER_CONSUMER_KEY'] ?? '', + $_ENV['MF_TWITTER_CONSUMER_SECRET'] ?? '', + $_ENV['MF_TWITTER_ACCESS_TOKEN'] ?? '', + $_ENV['MF_TWITTER_ACCESS_TOKEN_SECRET'] ?? '', $cache, true, false, - (bool) getenv('MF_TWITTER_EXTENDED_MODE') + (bool) ($_ENV['MF_TWITTER_EXTENDED_MODE'] ?? false) ); - array_push($feedProviders, $twitterProvider); + \array_push($feedProviders, $twitterProvider); } } - if (false !== $twitterSearch = getenv('MF_TWITTER_SEARCH_QUERY')) { - parse_str($twitterSearch, $searchParams); + + if (isset($_ENV['MF_TWITTER_SEARCH_QUERY'])) { + \parse_str($_ENV['MF_TWITTER_SEARCH_QUERY'], $searchParams); $twitterSearchProvider = new TwitterSearchFeed( $searchParams, - getenv('MF_TWITTER_CONSUMER_KEY'), - getenv('MF_TWITTER_CONSUMER_SECRET'), - getenv('MF_TWITTER_ACCESS_TOKEN'), - getenv('MF_TWITTER_ACCESS_TOKEN_SECRET'), + $_ENV['MF_TWITTER_CONSUMER_KEY'] ?? '', + $_ENV['MF_TWITTER_CONSUMER_SECRET'] ?? '', + $_ENV['MF_TWITTER_ACCESS_TOKEN'] ?? '', + $_ENV['MF_TWITTER_ACCESS_TOKEN_SECRET'] ?? '', $cache, - (bool) getenv('MF_TWITTER_EXTENDED_MODE') + (bool) ($_ENV['MF_TWITTER_EXTENDED_MODE'] ?? false) ); - array_push($feedProviders, $twitterSearchProvider); + \array_push($feedProviders, $twitterSearchProvider); } return $feedProviders; diff --git a/src/Exception/CredentialsException.php b/src/Exception/CredentialsException.php index d2902e4..5d0d42c 100644 --- a/src/Exception/CredentialsException.php +++ b/src/Exception/CredentialsException.php @@ -1,12 +1,17 @@ pageId = $pageId; @@ -61,15 +54,13 @@ public function __construct( 'status_type', 'message_tags', 'shares', - 'permalink_url' + 'permalink_url', ]; - $this->fields = array_unique(array_merge($this->fields, $fields)); + $this->fields = \array_unique(\array_merge($this->fields, $fields)); $this->apiBaseUrl = $apiBaseUrl ?: $this->apiBaseUrl; - if (null === $this->accessToken || - false === $this->accessToken || - empty($this->accessToken)) { - throw new CredentialsException("FacebookPageFeed needs a valid App access token.", 1); + if (empty($this->accessToken)) { + throw new CredentialsException('FacebookPageFeed needs a valid App access token.', 1); } } @@ -81,54 +72,57 @@ protected function getCacheKey(): string /** * @inheritDoc */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { $params = [ 'access_token' => $this->accessToken, - 'limit' => $count, - 'fields' => implode(',', $this->fields), + 'limit' => $count, + 'fields' => \implode(',', $this->fields), ]; /* * Filter by date range */ - if (null !== $this->since && - $this->since instanceof \Datetime) { + if ( + null !== $this->since && + $this->since instanceof DateTime + ) { $params['since'] = $this->since->getTimestamp(); } - if (null !== $this->until && - $this->until instanceof \Datetime) { + if ( + null !== $this->until && + $this->until instanceof DateTime + ) { $params['until'] = $this->until->getTimestamp(); } - $value = http_build_query($params, null, '&', PHP_QUERY_RFC3986); + $value = \http_build_query($params, '', '&', PHP_QUERY_RFC3986); yield new Request( 'GET', - $this->apiBaseUrl . $this->pageId . '/posts?'.$value + $this->apiBaseUrl . $this->pageId . '/posts?' . $value ); } - protected function getFeed($count = 5) + protected function getFeed(int $count = 5) { - $rawFeed = $this->getRawFeed($count); - if (is_array($rawFeed) && isset($rawFeed['error'])) { + $rawFeed = $this->getCachedRawFeed($count); + if (\is_array($rawFeed) && isset($rawFeed['error'])) { return $rawFeed; } + return $rawFeed->data; } /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): ?DateTime { - $date = new \DateTime(); - $date->setTimestamp(strtotime($item->created_time)); - return $date; + return new DateTime('@' . \strtotime($item->created_time)); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { return isset($item->message) ? $item->message : ''; } @@ -136,17 +130,15 @@ public function getCanonicalMessage($item) /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'facebook_page'; } /** * Gets the value of since. - * - * @return \Datetime */ - public function getSince() + public function getSince(): ?DateTime { return $this->since; } @@ -154,11 +146,9 @@ public function getSince() /** * Sets the value of since. * - * @param \Datetime $since the since - * - * @return self + * @param DateTime $since the since */ - public function setSince(\Datetime $since) + public function setSince(?DateTime $since): AbstractFeedProvider { $this->since = $since; @@ -167,10 +157,8 @@ public function setSince(\Datetime $since) /** * Gets the value of until. - * - * @return \Datetime */ - public function getUntil() + public function getUntil(): ?DateTime { return $this->until; } @@ -178,11 +166,9 @@ public function getUntil() /** * Sets the value of until. * - * @param \Datetime $until the until - * - * @return self + * @param DateTime $until the until */ - public function setUntil(\Datetime $until) + public function setUntil(?DateTime $until): AbstractFeedProvider { $this->until = $until; @@ -192,7 +178,7 @@ public function setUntil(\Datetime $until) /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->id); @@ -211,7 +197,7 @@ protected function createFeedItemFromObject($item): FeedItem } if (isset($item->message_tags)) { - $feedItem->setTags(array_map(function ($messageTag) { + $feedItem->setTags(\array_map(function ($messageTag) { return $messageTag->name; }, $item->message_tags)); } @@ -225,22 +211,20 @@ protected function createFeedItemFromObject($item): FeedItem return $feedItem; } - /** - * @return array - */ + /** @return string[] */ public function getFields(): array { return $this->fields; } /** - * @param array $fields + * @param string[] $fields * * @return FacebookPageFeed */ public function setFields(array $fields) { - $this->fields = array_unique($fields); + $this->fields = \array_unique($fields); return $this; } diff --git a/src/FeedProviderInterface.php b/src/FeedProviderInterface.php index 5a17a2f..7093c67 100644 --- a/src/FeedProviderInterface.php +++ b/src/FeedProviderInterface.php @@ -1,6 +1,7 @@ */ + public function getRequests(int $count = 5): Generator; /** * Get the social platform name. - * - * @return string */ - public function getFeedPlatform(); - /** - * - * Get item method must return the direct - * feed array and must inject two parameters in each item: - * - * * feedItemPlatform (string) - * * normalizedDate (\DateTime) - * - * @param integer $count - * @return array - * @throws FeedProviderErrorException - * @deprecated Use getCanonicalItems method - */ - public function getItems($count = 5); + public function getFeedPlatform(): string; /** * Get item method must return a normalized array of FeedItem. * - * @param int $count + * @return FeedItem[] * - * @return mixed * @throws FeedProviderErrorException */ - public function getCanonicalItems($count = 5); + public function getCanonicalItems(int $count = 5): array; /** - * Get a \DateTime object from a social feed item. + * Get a DateTime object from a social feed item. * * @param \stdClass $item - * @return \DateTime|null */ - public function getDateTime($item); + public function getDateTime($item): ?DateTime; /** * Check if the feed provider has succeded to * contact API. * * @param mixed $feed - * - * @return boolean */ - public function isValid($feed); + public function isValid($feed): bool; /** - * @param string $reason - * * @return $this */ public function addError(string $reason): FeedProviderInterface; /** * Get a canonical message from current feed item. - * - * @param \stdClass $item - * @return string */ - public function getCanonicalMessage($item); + public function getCanonicalMessage(stdClass $item): string; - /** - * @return bool - */ public function supportsRequestPool(): bool; } diff --git a/src/GithubCommitsFeed.php b/src/GithubCommitsFeed.php index dc98330..47e8e86 100644 --- a/src/GithubCommitsFeed.php +++ b/src/GithubCommitsFeed.php @@ -1,55 +1,48 @@ repository = $repository; $this->accessToken = $accessToken; $this->page = $page; - if (null === $repository || - false === $repository || - empty($repository)) { - throw new CredentialsException("GithubCommitsFeed needs a valid repository name.", 1); + if (empty($repository)) { + throw new CredentialsException('GithubCommitsFeed needs a valid repository name.', 1); } - if (0 === preg_match('#([a-zA-Z\-\_0-9\.]+)/([a-zA-Z\-\_0-9\.]+)#', $repository)) { - throw new CredentialsException("GithubCommitsFeed needs a valid repository name “user/project”.", 1); + if (0 === \preg_match('#([a-zA-Z\-\_0-9\.]+)/([a-zA-Z\-\_0-9\.]+)#', $repository)) { + throw new CredentialsException('GithubCommitsFeed needs a valid repository name “user/project”.', 1); } - if (null === $accessToken || - false === $accessToken || - empty($accessToken)) { - throw new CredentialsException("GithubCommitsFeed needs a valid access token.", 1); + if (empty($accessToken)) { + throw new CredentialsException('GithubCommitsFeed needs a valid access token.', 1); } } @@ -61,34 +54,32 @@ protected function getCacheKey(): string /** * @inheritDoc */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { - $value = http_build_query([ + $value = \http_build_query([ 'access_token' => $this->accessToken, - 'per_page' => $count, - 'token_type' => 'bearer', - 'page' => $this->page, - ], null, '&', PHP_QUERY_RFC3986); + 'per_page' => $count, + 'token_type' => 'bearer', + 'page' => $this->page, + ], '', '&', PHP_QUERY_RFC3986); yield new Request( 'GET', - 'https://api.github.com/repos/' . $this->repository . '/commits?'.$value + 'https://api.github.com/repos/' . $this->repository . '/commits?' . $value ); } /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): ?DateTime { - $date = new \DateTime(); - $date->setTimestamp(strtotime($item->commit->author->date)); - return $date; + return new DateTime('@' . \strtotime($item->commit->author->date)); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { return $item->commit->message; } @@ -96,7 +87,7 @@ public function getCanonicalMessage($item) /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'github_commit'; } @@ -104,12 +95,13 @@ public function getFeedPlatform() /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->sha); $feedItem->setAuthor($item->commit->author->name); $feedItem->setLink($item->html_url); + return $feedItem; } } diff --git a/src/GithubReleasesFeed.php b/src/GithubReleasesFeed.php index 4e17664..cc472b0 100644 --- a/src/GithubReleasesFeed.php +++ b/src/GithubReleasesFeed.php @@ -1,56 +1,48 @@ repository = $repository; $this->accessToken = $accessToken; $this->page = $page; - if (null === $repository || - false === $repository || - empty($repository)) { - throw new CredentialsException("GithubReleasesFeed needs a valid repository name.", 1); + if (empty($repository)) { + throw new CredentialsException('GithubReleasesFeed needs a valid repository name.', 1); } - if (0 === preg_match('#([a-zA-Z\-\_0-9\.]+)/([a-zA-Z\-\_0-9\.]+)#', $repository)) { - throw new CredentialsException("GithubReleasesFeed needs a valid repository name “user/project”.", 1); + if (0 === \preg_match('#([a-zA-Z\-\_0-9\.]+)/([a-zA-Z\-\_0-9\.]+)#', $repository)) { + throw new CredentialsException('GithubReleasesFeed needs a valid repository name “user/project”.', 1); } - if (null === $accessToken || - false === $accessToken || - empty($accessToken)) { - throw new CredentialsException("GithubReleasesFeed needs a valid access token.", 1); + if (empty($accessToken)) { + throw new CredentialsException('GithubReleasesFeed needs a valid access token.', 1); } } @@ -62,34 +54,32 @@ protected function getCacheKey(): string /** * @inheritDoc */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { - $value = http_build_query([ + $value = \http_build_query([ 'access_token' => $this->accessToken, - 'per_page' => $count, - 'token_type' => 'bearer', - 'page' => $this->page, - ], null, '&', PHP_QUERY_RFC3986); + 'per_page' => $count, + 'token_type' => 'bearer', + 'page' => $this->page, + ], '', '&', PHP_QUERY_RFC3986); yield new Request( 'GET', - 'https://api.github.com/repos/' . $this->repository . '/releases?'.$value + 'https://api.github.com/repos/' . $this->repository . '/releases?' . $value ); } /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): ?DateTime { - $date = new \DateTime(); - $date->setTimestamp(strtotime($item->created_at)); - return $date; + return new DateTime('@' . \strtotime($item->created_at)); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { return $item->name; } @@ -97,7 +87,7 @@ public function getCanonicalMessage($item) /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'github_release'; } @@ -105,7 +95,7 @@ public function getFeedPlatform() /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->id); @@ -113,6 +103,7 @@ protected function createFeedItemFromObject($item): FeedItem $feedItem->setLink($item->html_url); $feedItem->setTitle($item->name); $feedItem->setMessage($item->body); + return $feedItem; } } diff --git a/src/Graph/AccessToken.php b/src/Graph/AccessToken.php index 9746191..eae1080 100644 --- a/src/Graph/AccessToken.php +++ b/src/Graph/AccessToken.php @@ -1,29 +1,16 @@ accessToken = $accessToken; $this->tokenType = $tokenType; @@ -31,8 +18,6 @@ final public function __construct(string $accessToken, string $tokenType = "", i } /** - * @param array $payload - * * @return static */ public static function fromArray(array $payload) @@ -49,25 +34,16 @@ public function __toString() return $this->accessToken; } - /** - * @return string - */ public function getAccessToken(): string { return $this->accessToken; } - /** - * @return string - */ public function getTokenType(): string { return $this->tokenType; } - /** - * @return int - */ public function getExpiresIn(): int { return $this->expiresIn; diff --git a/src/Graph/RefreshInstagramAccessToken.php b/src/Graph/RefreshInstagramAccessToken.php index 43a19c3..cf5eedf 100644 --- a/src/Graph/RefreshInstagramAccessToken.php +++ b/src/Graph/RefreshInstagramAccessToken.php @@ -1,40 +1,29 @@ accessToken = $accessToken; } - /** - * @throws FeedProviderErrorException - */ + /** @throws FeedProviderErrorException */ public function getRefreshedAccessToken(): AccessToken { $value = \http_build_query([ - 'grant_type' => static::$grantType, + 'grant_type' => self::$grantType, 'access_token' => $this->accessToken, ]); @@ -44,9 +33,12 @@ public function getRefreshedAccessToken(): AccessToken ]); $response = $client->send(new Request( 'GET', - 'https://graph.instagram.com/refresh_access_token?'.$value + 'https://graph.instagram.com/refresh_access_token?' . $value )); - return AccessToken::fromArray(json_decode($response->getBody()->getContents(), true)); + + $body = GuzzleUtils::jsonDecode($response->getBody()->getContents(), true); + + return AccessToken::fromArray((array) $body); } catch (GuzzleException $e) { throw new FeedProviderErrorException(RefreshInstagramAccessToken::class, $e->getMessage(), $e); } diff --git a/src/GraphInstagramFeed.php b/src/GraphInstagramFeed.php index e05223d..02fd83d 100644 --- a/src/GraphInstagramFeed.php +++ b/src/GraphInstagramFeed.php @@ -1,54 +1,56 @@ userId = $userId; $this->accessToken = $accessToken; - if (count($fields) > 0) { - $this->fields = $fields; - } else { - $this->fields = [ + + $this->fields = \count($fields) > 0 + ? $fields + : [ 'id', 'username', 'caption', @@ -60,101 +62,94 @@ public function __construct($userId, $accessToken, CacheProvider $cacheProvider 'like_count', 'comments_count', ]; - } - if (null === $this->accessToken || - false === $this->accessToken || - empty($this->accessToken)) { - throw new CredentialsException("GraphInstagramFeed needs a valid access token.", 1); + if (empty($this->accessToken)) { + throw new CredentialsException('GraphInstagramFeed needs a valid access token.', 1); } } - protected function getCacheKey(): string + /** @inheritDoc */ + public function getRequests(int $count = 5): Generator { - return $this->getFeedPlatform() . $this->userId; + $value = \http_build_query([ + 'fields' => \implode(',', $this->fields), + 'access_token' => $this->accessToken, + 'limit' => $count, + ], '', '&', PHP_QUERY_RFC3986); + + yield new Request('GET', 'https://graph.instagram.com/' . $this->userId . '/media?' . $value); } - /** - * @inheritDoc - */ - public function getRequests($count = 5): \Generator + protected function getCacheKey(): string { - $value = http_build_query([ - 'fields' => implode(',', $this->fields), - 'access_token' => $this->accessToken, - 'limit' => $count, - ], null, '&', PHP_QUERY_RFC3986); - yield new Request( - 'GET', - 'https://graph.instagram.com/' . $this->userId . '/media?'.$value - ); + return $this->getFeedPlatform() . $this->userId; } - protected function getFeed($count = 5) + protected function getFeed(int $count = 5) { - $rawFeed = $this->getRawFeed($count); + $rawFeed = $this->getCachedRawFeed($count); + if ($this->isValid($rawFeed)) { return $rawFeed->data; } + return []; } /** * {@inheritdoc} */ - public function isValid($feed) + public function isValid($feed): bool { - if (count($this->errors) > 0) { - throw new FeedProviderErrorException($this->getFeedPlatform(), implode(', ', $this->errors)); + if (\count($this->errors) > 0) { + throw new FeedProviderErrorException($this->getFeedPlatform(), \implode(', ', $this->errors)); } - return isset($feed->data) && is_iterable($feed->data); + + return isset($feed->data) && \is_iterable($feed->data); } /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): DateTime { - return new \DateTime($item->timestamp); + return new DateTime($item->timestamp); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { if (null !== $item->caption) { return $item->caption; } - return ""; + return ''; } - /** - * {@inheritdoc} - */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'instagram'; } - /** - * @inheritDoc - */ - protected function createFeedItemFromObject($item): FeedItem + /** @inheritDoc */ + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->id); $feedItem->setAuthor($item->username); $feedItem->setLink($item->permalink); + if (isset($item->like_count)) { $feedItem->setLikeCount($item->like_count); } + if (isset($item->comments_count)) { $feedItem->setShareCount($item->comments_count); } - if ($item->media_type === static::TYPE_VIDEO && !empty($item->thumbnail_url)) { + if (self::TYPE_VIDEO === $item->media_type && !empty($item->thumbnail_url)) { $feedItemImage = new Image(); $feedItemImage->setUrl($item->thumbnail_url); $feedItem->addImage($feedItemImage); diff --git a/src/InstagramFeed.php b/src/InstagramFeed.php index f6ed374..58cc45b 100644 --- a/src/InstagramFeed.php +++ b/src/InstagramFeed.php @@ -1,42 +1,40 @@ userId = $userId; $this->accessToken = $accessToken; - if (null === $this->accessToken || - false === $this->accessToken || - empty($this->accessToken)) { - throw new CredentialsException("InstagramFeed needs a valid access token.", 1); + if (empty($this->accessToken)) { + throw new CredentialsException('InstagramFeed needs a valid access token.', 1); } } @@ -48,64 +46,64 @@ protected function getCacheKey(): string /** * @inheritDoc */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { - $value = http_build_query([ + $value = \http_build_query([ 'access_token' => $this->accessToken, - 'count' => $count, - ], null, '&', PHP_QUERY_RFC3986); + 'count' => $count, + ], '', '&', PHP_QUERY_RFC3986); yield new Request( 'GET', - 'https://api.instagram.com/v1/users/' . $this->userId . '/media/recent/?'.$value + 'https://api.instagram.com/v1/users/' . $this->userId . '/media/recent/?' . $value ); } - protected function getFeed($count = 5) + protected function getFeed(int $count = 5) { - $rawFeed = $this->getRawFeed($count); + $rawFeed = $this->getCachedRawFeed($count); if ($this->isValid($rawFeed)) { return $rawFeed->data; } + return []; } /** * {@inheritdoc} */ - public function isValid($feed) + public function isValid($feed): bool { - if (count($this->errors) > 0) { - throw new FeedProviderErrorException($this->getFeedPlatform(), implode(', ', $this->errors)); + if (\count($this->errors) > 0) { + throw new FeedProviderErrorException($this->getFeedPlatform(), \implode(', ', $this->errors)); } - return isset($feed->data) && is_iterable($feed->data); + + return isset($feed->data) && \is_iterable($feed->data); } /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): DateTime { - $date = new \DateTime(); - $date->setTimestamp($item->created_time); - return $date; + return new DateTime('@' . $item->created_time); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { if (null !== $item->caption) { return $item->caption->text; } - return ""; + return ''; } /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'instagram'; } @@ -113,7 +111,7 @@ public function getFeedPlatform() /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->id); @@ -127,6 +125,7 @@ protected function createFeedItemFromObject($item): FeedItem $feedItemImage->setWidth($item->images->standard_resolution->width); $feedItemImage->setHeight($item->images->standard_resolution->height); $feedItem->addImage($feedItemImage); + return $feedItem; } } diff --git a/src/InstagramOEmbedFeed.php b/src/InstagramOEmbedFeed.php index 95b1a47..fd67e84 100644 --- a/src/InstagramOEmbedFeed.php +++ b/src/InstagramOEmbedFeed.php @@ -1,30 +1,33 @@ $url) { - if (0 === preg_match('#^https?:\/\/www\.instagram\.com\/p\/#', $url)) { + if (0 === \preg_match('#^https?:\/\/www\.instagram\.com\/p\/#', $url)) { $embedUrls[$i] = 'https://www.instagram.com/p/' . $url; } } @@ -33,23 +36,18 @@ public function __construct($embedUrls, CacheProvider $cacheProvider = null) protected function getCacheKey(): string { - return $this->getFeedPlatform() . serialize($this->embedUrls); + return $this->getFeedPlatform() . \serialize($this->embedUrls); } - /** - * @param int $count - * - * @return \Generator - */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { foreach ($this->embedUrls as $embedUrl) { - $value = http_build_query([ + $value = \http_build_query([ 'url' => $embedUrl, - ], null, '&', PHP_QUERY_RFC3986); + ], '', '&', PHP_QUERY_RFC3986); yield new Request( 'GET', - 'https://api.instagram.com/oembed?'.$value + 'https://api.instagram.com/oembed?' . $value ); } } @@ -57,51 +55,32 @@ public function getRequests($count = 5): \Generator /** * @param mixed $rawFeed * @param bool $json - * - * @return AbstractFeedProvider */ public function setRawFeed($rawFeed, $json = true): AbstractFeedProvider { if (null === $this->rawFeed) { $this->rawFeed = []; } - if ($json === true) { - $rawFeed = json_decode($rawFeed); - if ('No error' !== $jsonError = json_last_error_msg()) { - throw new \RuntimeException($jsonError); - } + if (true === $json && \is_string($rawFeed)) { + GuzzleUtils::jsonDecode($rawFeed, true); } - array_push($this->rawFeed, $rawFeed); + \array_push($this->rawFeed, $rawFeed); + return $this; } /** - * @param int $count - * * @return array + * * @throws FeedProviderErrorException */ - protected function getRawFeed($count = 5) + protected function getRawFeed(int $count = 5) { - $countKey = $this->getCacheKey() . $count; - if (null !== $this->rawFeed) { - if (null !== $this->cacheProvider && - !$this->cacheProvider->contains($countKey)) { - $this->cacheProvider->save( - $countKey, - $this->rawFeed, - $this->ttl - ); - } return $this->rawFeed; } try { - if (null !== $this->cacheProvider && - $this->cacheProvider->contains($countKey)) { - return $this->cacheProvider->fetch($countKey); - } $body = []; $promises = []; $client = new \GuzzleHttp\Client(); @@ -109,28 +88,17 @@ protected function getRawFeed($count = 5) // Initiate each request but do not block $promises[] = $client->sendAsync($request); } - $responses = \GuzzleHttp\Promise\settle($promises)->wait(); + /** @var array $responses */ + $responses = Promise\Utils::settle($promises)->wait(); - /** @var array $response */ foreach ($responses as $response) { - if ($response['state'] !== 'rejected') { - array_push($body, json_decode($response['value']->getBody()->getContents())); - if ('No error' !== $jsonError = json_last_error_msg()) { - throw new \RuntimeException($jsonError); - } + if ('rejected' !== $response['state']) { + \array_push($body, GuzzleUtils::jsonDecode($response['value']->getBody()->getContents())); } else { throw $response['reason']; } } - if (null !== $this->cacheProvider) { - $this->cacheProvider->save( - $countKey, - $body, - $this->ttl - ); - } - return $body; } catch (ClientException $e) { throw new FeedProviderErrorException($this->getFeedPlatform(), $e->getMessage(), $e); @@ -140,7 +108,7 @@ protected function getRawFeed($count = 5) /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'instagram_oembed'; } @@ -148,10 +116,10 @@ public function getFeedPlatform() /** * {@inheritdoc} */ - public function isValid($feed) + public function isValid($feed): bool { - if (count($this->errors) > 0) { - throw new FeedProviderErrorException($this->getFeedPlatform(), implode(', ', $this->errors)); + if (\count($this->errors) > 0) { + throw new FeedProviderErrorException($this->getFeedPlatform(), \implode(', ', $this->errors)); } // OEmbed response is not iterable because there is only one item return null !== $feed; @@ -160,30 +128,31 @@ public function isValid($feed) /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): ?DateTime { - if (false !== preg_match("#datetime=\\\"([^\"]+)\\\"#", $item->html, $matches)) { - return new \DateTime($matches[1]); + if (false !== \preg_match('#datetime=\\"([^"]+)\\"#', $item->html, $matches)) { + return new DateTime('@' . $matches[1]); } + return null; } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { if (isset($item->title)) { return $item->title; } - return ""; + return ''; } /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->media_id); @@ -194,6 +163,7 @@ protected function createFeedItemFromObject($item): FeedItem $feedItemImage->setWidth($item->thumbnail_width); $feedItemImage->setHeight($item->thumbnail_height); $feedItem->addImage($feedItemImage); + return $feedItem; } } diff --git a/src/MediumFeed.php b/src/MediumFeed.php index e01cd2b..e5db64b 100644 --- a/src/MediumFeed.php +++ b/src/MediumFeed.php @@ -1,50 +1,46 @@ username = $username; $this->cacheProvider = $cacheProvider; $this->userId = $userId; - if ($this->userId !== null) { + if (null !== $this->userId) { /* * If userId is available, use the profile/stream endpoint instead for better consistency * between calls. @@ -63,14 +59,14 @@ protected function getCacheKey(): string /** * @inheritDoc */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { - $value = http_build_query([ - 'format' => 'json', - 'limit' => $count, + $value = \http_build_query([ + 'format' => 'json', + 'limit' => $count, 'collectionId' => null, - 'source' => 'latest', - ], null, '&', PHP_QUERY_RFC3986); + 'source' => 'latest', + ], '', '&', PHP_QUERY_RFC3986); yield new Request( 'GET', $this->url . '?' . $value @@ -80,57 +76,27 @@ public function getRequests($count = 5): \Generator /** * @inheritDoc */ - public function setRawFeed($rawFeed, $json = true): AbstractFeedProvider + public function setRawFeed(string $rawFeed): AbstractFeedProvider { - if ($json === true) { - $rawFeed = str_replace('])}while(1);', '', $rawFeed); - $rawFeed = json_decode($rawFeed); - if ('No error' !== $jsonError = json_last_error_msg()) { - throw new \RuntimeException($jsonError); - } - } + $rawFeed = \str_replace('])}while(1);', '', $rawFeed); + $rawFeed = Utils::jsonDecode($rawFeed, true); $this->rawFeed = $rawFeed; + return $this; } - - protected function getRawFeed($count = 5) + protected function getRawFeed(int $count = 5) { - $countKey = $this->getCacheKey() . $count; - if (null !== $this->rawFeed) { - if (null !== $this->cacheProvider && - !$this->cacheProvider->contains($countKey)) { - $this->cacheProvider->save( - $countKey, - $this->rawFeed, - $this->ttl - ); - } return $this->rawFeed; } - try { - if (null !== $this->cacheProvider && - $this->cacheProvider->contains($countKey)) { - return $this->cacheProvider->fetch($countKey); - } + try { $client = new Client(); $response = $client->send($this->getRequests($count)->current()); $raw = $response->getBody()->getContents(); - $raw = str_replace('])}while(1);', '', $raw); - $body = json_decode($raw); - if ('No error' !== $jsonError = json_last_error_msg()) { - throw new \RuntimeException($jsonError); - } - - if (null !== $this->cacheProvider) { - $this->cacheProvider->save( - $countKey, - $body, - $this->ttl - ); - } + $raw = \str_replace('])}while(1);', '', $raw); + $body = Utils::jsonDecode($raw); return $body; } catch (ClientException $e) { @@ -141,28 +107,31 @@ protected function getRawFeed($count = 5) /** * @inheritDoc */ - protected function getFeed($count = 5): array + protected function getFeed(int $count = 5): array { - return $this->getTypedFeed($this->getRawFeed($count)); + return $this->getTypedFeed($this->getCachedRawFeed($count)); } - /** * @param object $body * * @return array + * * @throws \Exception */ protected function getTypedFeed($body) { $feed = []; + if (!isset($body->payload)) { + return $feed; + } if (isset($body->payload->user)) { $this->name = $body->payload->user->name; } foreach ($body->payload->streamItems as $item) { - if ($item->itemType === 'postPreview') { + if ('postPreview' === $item->itemType) { $id = $item->postPreview->postId; - $createdAt = new \DateTime(); + $createdAt = new DateTime(); $createdAt->setTimestamp($item->createdAt); if (isset($body->payload->references->Post->$id)) { @@ -177,7 +146,7 @@ protected function getTypedFeed($body) /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'medium'; } @@ -185,38 +154,34 @@ public function getFeedPlatform() /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): ?DateTime { - $createdAt = new \DateTime(); - if ($this->isUsingLatestPublicationDate()) { - $createdAt->setTimestamp($item->latestPublishedAt/1000); - } else { - $createdAt->setTimestamp($item->firstPublishedAt/1000); - } - return $createdAt; + $createdAt = $this->isUsingLatestPublicationDate() ? $item->latestPublishedAt / 1000 : $item->firstPublishedAt / 1000; + + return new DateTime('@' . $createdAt); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { if (isset($item->title)) { return $item->title; } - return ""; + return ''; } /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->uniqueSlug); $feedItem->setAuthor($this->name); - $feedItem->setLink('https://medium.com/'.$this->username.'/'.$item->uniqueSlug); + $feedItem->setLink('https://medium.com/' . $this->username . '/' . $item->uniqueSlug); $feedItem->setTitle($item->title); $feedItem->setTags($item->virtuals->tags); if (isset($item->content) && isset($item->content->subtitle)) { @@ -228,7 +193,7 @@ protected function createFeedItemFromObject($item): FeedItem /* * 4 seems to be an image type */ - if ($paragraph->type === 4 && isset($paragraph->metadata)) { + if (4 === $paragraph->type && isset($paragraph->metadata)) { $feedItemImage = new Image(); $feedItemImage->setUrl('https://miro.medium.com/' . $paragraph->metadata->id); $feedItemImage->setWidth($paragraph->metadata->originalWidth); @@ -237,6 +202,7 @@ protected function createFeedItemFromObject($item): FeedItem } } } + return $feedItem; } @@ -248,19 +214,11 @@ public function getName() return $this->name; } - /** - * @return bool - */ public function isUsingLatestPublicationDate(): bool { return $this->useLatestPublicationDate; } - /** - * @param bool $useLatestPublicationDate - * - * @return MediumFeed - */ public function setUseLatestPublicationDate(bool $useLatestPublicationDate): MediumFeed { $this->useLatestPublicationDate = $useLatestPublicationDate; diff --git a/src/MixedFeed.php b/src/MixedFeed.php index 226f093..3d8adc6 100644 --- a/src/MixedFeed.php +++ b/src/MixedFeed.php @@ -1,48 +1,41 @@ providers) > 0) { - $perProviderCount = floor($count / count($this->providers)); - - /** @var FeedProviderInterface $provider */ - foreach ($this->providers as $provider) { - try { - $list = array_merge($list, $provider->getItems($perProviderCount)); - } catch (FeedProviderErrorException $e) { - $list = array_merge($list, [ - new ErroredFeedItem($e->getMessage(), $provider->getFeedPlatform()), - ]); - } - } - } - - return $this->sortFeedObjects($list); - } - - /** - * @inheritDoc + * @return FeedItem[] */ - public function getCanonicalItems($count = 5) + public function getCanonicalItems(int $count = 5): array { $list = []; - if (count($this->providers) > 0) { - $perProviderCount = floor($count / count($this->providers)); + if (\count($this->providers) > 0) { + $perProviderCount = (int) \floor($count / \count($this->providers)); /** @var FeedProviderInterface $provider */ foreach ($this->providers as $provider) { try { - $list = array_merge($list, $provider->getCanonicalItems($perProviderCount)); + /** @var FeedItem[] $list */ + $list = \array_merge($list, $provider->getCanonicalItems($perProviderCount)); } catch (FeedProviderErrorException $e) { $errorItem = new FeedItem(); $errorItem->setMessage($e->getMessage()); $errorItem->setPlatform($provider->getFeedPlatform() . ' [errored]'); - $errorItem->setDateTime(new \DateTime()); - $list = array_merge($list, [ - $errorItem + $errorItem->setDateTime(new DateTime()); + $list = \array_merge($list, [ + $errorItem, ]); } } } + return $this->sortFeedItems($list); } /** * @param FeedItem[] $feedItems * - * @return array + * @return FeedItem[] $feedItems */ protected function sortFeedItems(array $feedItems): array { - usort($feedItems, function (FeedItem $a, FeedItem $b) { + \usort($feedItems, function (FeedItem $a, FeedItem $b) { $aDT = $a->getDateTime(); $bDT = $b->getDateTime(); @@ -122,92 +93,19 @@ protected function sortFeedItems(array $feedItems): array // DESC sorting return ($aDT > $bDT) ? -1 : 1; }); - return $feedItems; - } - - /** - * @param \stdClass[] $items - * - * @return array - */ - protected function sortFeedObjects(array $items) - { - usort($items, function (\stdClass $a, \stdClass $b) { - $aDT = $a->normalizedDate; - $bDT = $b->normalizedDate; - - if ($aDT == $bDT) { - return 0; - } - // ASC sorting - if ($this->sortDirection === static::ASC) { - return ($aDT > $bDT) ? 1 : -1; - } - // DESC sorting - return ($aDT > $bDT) ? -1 : 1; - }); - - return $items; - } - - /** - * {@inheritdoc} - */ - public function getFeedPlatform() - { - return 'mixed'; - } - - /** - * {@inheritdoc} - */ - public function getDateTime($item) - { - return new \DateTime('now'); - } - - /** - * {@inheritdoc} - */ - public function getCanonicalMessage($item) - { - return ""; - } - - /** - * {@inheritdoc} - */ - public function isValid($feed) - { - return true; - } - /** - * {@inheritdoc} - */ - public function getErrors($feed) - { - return ''; - } - - /** - * @inheritDoc - */ - protected function getFeed($count = 5): array - { - trigger_error('getFeed method must not be called in MixedFeed.', E_USER_ERROR); - return []; + return $feedItems; } /** - * @inheritDoc + * @return FeedItem[] */ - public function getAsyncCanonicalItems($count = 5): array + public function getAsyncCanonicalItems(int $count = 5): array { - if (count($this->providers) === 0) { + if (0 === \count($this->providers)) { throw new \RuntimeException('No provider were registered'); } - $perProviderCount = floor($count / count($this->providers)); + $perProviderCount = (int) \floor($count / \count($this->providers)); $list = []; $requests = []; /** @var FeedProviderInterface $provider */ @@ -223,22 +121,25 @@ public function getAsyncCanonicalItems($count = 5): array $client = new Client(); $pool = new Pool($client, $requests, [ 'concurrency' => 6, - 'fulfilled' => function ($response, $index) { - list($providerIdx, $i) = explode('.', $index); + 'fulfilled' => function (Response $response, $index) { + list($providerIdx, $i) = \explode('.', $index); $provider = $this->providers[$providerIdx]; - if ($provider instanceof AbstractFeedProvider && - $response instanceof Response && - $response->getStatusCode() === 200) { + if ( + 200 === $response->getStatusCode() + && $provider instanceof AbstractFeedProvider + && $response instanceof Response + ) { $provider->setRawFeed($response->getBody()->getContents()); } else { $provider->addError($response->getReasonPhrase()); } }, - 'rejected' => function ($reason, $index) { - list($providerIdx, $i) = explode('.', $index); + 'rejected' => function (RequestException $reason, $index) { + list($providerIdx, $i) = \explode('.', $index); $provider = $this->providers[$providerIdx]; - if ($provider instanceof AbstractFeedProvider && - method_exists($reason, 'getMessage')) { + if ( + $provider instanceof AbstractFeedProvider + ) { $provider->addError($reason->getMessage()); } }, @@ -253,37 +154,19 @@ public function getAsyncCanonicalItems($count = 5): array * For providers which already have a cached response */ try { - $list = array_merge($list, $provider->getCanonicalItems($perProviderCount)); + /** @var FeedItem[] $list */ + $list = \array_merge($list, $provider->getCanonicalItems($perProviderCount)); } catch (FeedProviderErrorException $e) { $errorItem = new FeedItem(); $errorItem->setMessage($e->getMessage()); $errorItem->setPlatform($provider->getFeedPlatform() . ' [errored]'); - $errorItem->setDateTime(new \DateTime()); - $list = array_merge($list, [ - $errorItem + $errorItem->setDateTime(new DateTime()); + $list = \array_merge($list, [ + $errorItem, ]); } } return $this->sortFeedItems($list); } - - /** - * @param int $count - * - * @return \Generator - */ - public function getRequests($count = 5): \Generator - { - if (count($this->providers) === 0) { - throw new \RuntimeException('No provider were registered'); - } - - $perProviderCount = floor($count / count($this->providers)); - - /** @var FeedProviderInterface $provider */ - foreach ($this->providers as $provider) { - yield iterator_to_array($provider->getRequests($perProviderCount)); - } - } } diff --git a/src/MockObject/ErroredFeedItem.php b/src/MockObject/ErroredFeedItem.php deleted file mode 100644 index 0c65499..0000000 --- a/src/MockObject/ErroredFeedItem.php +++ /dev/null @@ -1,34 +0,0 @@ -message = $message; - $this->feedItemPlatform = $feedItemPlatform . '[errored]'; - $this->normalizedDate = new \Datetime('now'); - $this->canonicalMessage = $message; - } -} diff --git a/src/PinterestBoardFeed.php b/src/PinterestBoardFeed.php index 2ed8c8e..138f2ad 100644 --- a/src/PinterestBoardFeed.php +++ b/src/PinterestBoardFeed.php @@ -1,12 +1,16 @@ boardId = $boardId; $this->accessToken = $accessToken; - if (null === $this->accessToken || - false === $this->accessToken || - empty($this->accessToken)) { - throw new CredentialsException("PinterestBoardFeed needs a valid access token.", 1); + if (empty($this->accessToken)) { + throw new CredentialsException('PinterestBoardFeed needs a valid access token.', 1); } } @@ -50,58 +49,58 @@ protected function getCacheKey(): string /** * @inheritDoc */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { - $value = http_build_query([ + $value = \http_build_query([ 'access_token' => $this->accessToken, - 'limit' => $count, - 'fields' => 'id,color,created_at,creator,media,image[original],note,link,url', - ], null, '&', PHP_QUERY_RFC3986); + 'limit' => $count, + 'fields' => 'id,color,created_at,creator,media,image[original],note,link,url', + ], '', '&', PHP_QUERY_RFC3986); yield new Request( 'GET', - 'https://api.pinterest.com/v1/boards/' . $this->boardId . '/pins?'.$value + 'https://api.pinterest.com/v1/boards/' . $this->boardId . '/pins?' . $value ); } /** * {@inheritdoc} */ - public function isValid($feed) + public function isValid($feed): bool { - if (count($this->errors) > 0) { - throw new FeedProviderErrorException($this->getFeedPlatform(), implode(', ', $this->errors)); + if (\count($this->errors) > 0) { + throw new FeedProviderErrorException($this->getFeedPlatform(), \implode(', ', $this->errors)); } - return isset($feed->data) && is_iterable($feed->data); + + return isset($feed->data) && \is_iterable($feed->data); } /** - * @param int $count * @return mixed + * * @throws FeedProviderErrorException */ - protected function getFeed($count = 5) + protected function getFeed(int $count = 5) { - $rawFeed = $this->getRawFeed($count); + $rawFeed = $this->getCachedRawFeed($count); if ($this->isValid($rawFeed)) { return $rawFeed->data; } + return []; } /** * {@inheritdoc} */ - public function getDateTime($item) + public function getDateTime($item): DateTime { - $date = new \DateTime(); - $date->setTimestamp(strtotime($item->created_at)); - return $date; + return new DateTime('@' . \strtotime($item->created_at)); } /** * {@inheritdoc} */ - public function getCanonicalMessage($item) + public function getCanonicalMessage(stdClass $item): string { return $item->note; } @@ -109,7 +108,7 @@ public function getCanonicalMessage($item) /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'pinterest_pin'; } @@ -117,7 +116,7 @@ public function getFeedPlatform() /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->id); @@ -131,6 +130,7 @@ protected function createFeedItemFromObject($item): FeedItem $feedItemImage->setHeight($item->image->original->height); $feedItem->addImage($feedItemImage); } + return $feedItem; } } diff --git a/src/Response/FeedItemResponse.php b/src/Response/FeedItemResponse.php index d831556..cca542d 100644 --- a/src/Response/FeedItemResponse.php +++ b/src/Response/FeedItemResponse.php @@ -1,21 +1,20 @@ meta = $meta; } - /** - * @return array - */ + /** @return FeedItem[] */ public function getItems(): array { return $this->items; } - /** - * @param array $items - * - * @return FeedItemResponse - */ + /** @param FeedItem[] $items */ public function setItems(array $items): FeedItemResponse { $this->items = $items; @@ -43,19 +36,13 @@ public function setItems(array $items): FeedItemResponse return $this; } - /** - * @return array - */ + /** @return mixed[] */ public function getMeta(): array { return $this->meta; } - /** - * @param array $meta - * - * @return FeedItemResponse - */ + /** @param mixed[] $meta */ public function setMeta(array $meta): FeedItemResponse { $this->meta = $meta; diff --git a/src/TwitterFeed.php b/src/TwitterFeed.php index d299b83..4a3e6f6 100644 --- a/src/TwitterFeed.php +++ b/src/TwitterFeed.php @@ -1,8 +1,10 @@ getFeedPlatform() . $this->userId; } - protected function getFeed($count = 5) + protected function getRawFeed(int $count = 5) { - $countKey = $this->getCacheKey() . $count; - try { - if (null !== $this->cacheProvider && - $this->cacheProvider->contains($countKey)) { - return $this->cacheProvider->fetch($countKey); - } - $body = $this->twitterConnection->get("statuses/user_timeline", [ - "user_id" => $this->userId, - "count" => $count, - "exclude_replies" => $this->excludeReplies, - 'include_rts' => $this->includeRts, - 'tweet_mode' => ($this->extended ? 'extended' : '') + $body = $this->twitterConnection->get('statuses/user_timeline', [ + 'user_id' => $this->userId, + 'count' => $count, + 'exclude_replies' => $this->excludeReplies, + 'include_rts' => $this->includeRts, + 'tweet_mode' => ($this->extended ? 'extended' : ''), ]); - if (null !== $this->cacheProvider) { - $this->cacheProvider->save( - $countKey, - $body, - $this->ttl - ); - } + return $body; } catch (TwitterOAuthException $e) { throw new FeedProviderErrorException($this->getFeedPlatform(), $e->getMessage(), $e); diff --git a/src/TwitterSearchFeed.php b/src/TwitterSearchFeed.php index fabe06b..f3ba405 100644 --- a/src/TwitterSearchFeed.php +++ b/src/TwitterSearchFeed.php @@ -1,8 +1,9 @@ queryParams = array_filter($queryParams); + $this->queryParams = \array_filter($queryParams); $this->extended = $extended; } protected function getCacheKey(): string { - return $this->getFeedPlatform() . md5(serialize($this->queryParams)); + return $this->getFeedPlatform() . \md5(\serialize($this->queryParams)); } - /** - * @return string - */ - protected function formatQueryParams() + protected function formatQueryParams(): string { $inlineParams = []; foreach ($this->queryParams as $key => $value) { - if (is_numeric($key)) { + if (\is_numeric($key)) { $inlineParams[] = $value; } else { $inlineParams[] = $key . ':' . $value; } } - return implode(' ', $inlineParams); + return \implode(' ', $inlineParams); } - protected function getFeed($count = 5) + protected function getRawFeed(int $count = 5) { - $countKey = $this->getCacheKey() . $count; - try { - if (null !== $this->cacheProvider && - $this->cacheProvider->contains($countKey)) { - return $this->cacheProvider->fetch($countKey); - } - - if ($this->includeRetweets === false) { + if (false === $this->includeRetweets) { $this->queryParams['-filter'] = 'retweets'; } $params = [ - "q" => $this->formatQueryParams(), - "count" => $count, - "result_type" => $this->resultType, + 'q' => $this->formatQueryParams(), + 'count' => $count, + 'result_type' => $this->resultType, ]; if ($this->extended) { $params['tweet_mode'] = 'extended'; } /** @var object $body */ - $body = $this->twitterConnection->get("search/tweets", $params); - - if (null !== $this->cacheProvider) { - $this->cacheProvider->save( - $countKey, - $body->statuses, - $this->ttl - ); - } - return $body->statuses; + $body = $this->twitterConnection->get('search/tweets', $params); + + return isset($body->statuses) ? $body->statuses : []; } catch (TwitterOAuthException $e) { throw new FeedProviderErrorException($this->getFeedPlatform(), $e->getMessage(), $e); } } - /** - * @return bool - */ - public function isIncludeRetweets() + public function isIncludeRetweets(): bool { return $this->includeRetweets; } /** - * @param bool $includeRetweets * @return TwitterSearchFeed */ - public function setIncludeRetweets($includeRetweets) + public function setIncludeRetweets(bool $includeRetweets): AbstractFeedProvider { $this->includeRetweets = $includeRetweets; + return $this; } - /** - * @return string - */ - public function getResultType() + public function getResultType(): string { return $this->resultType; } @@ -158,12 +115,12 @@ public function getResultType() * recent : return only the most recent results in the response * popular : return only the most popular results in the response. * - * @param string $resultType * @return TwitterSearchFeed */ - public function setResultType($resultType) + public function setResultType(string $resultType): AbstractFeedProvider { $this->resultType = $resultType; + return $this; } } diff --git a/src/YoutubeMostPopularFeed.php b/src/YoutubeMostPopularFeed.php index 68ae182..84d7bab 100644 --- a/src/YoutubeMostPopularFeed.php +++ b/src/YoutubeMostPopularFeed.php @@ -1,6 +1,8 @@ getFeedPlatform() . serialize($this->apiKey); + return $this->getFeedPlatform() . \serialize($this->apiKey); } - /** - * @param int $count - * - * @return \Generator - */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { - $value = http_build_query([ - 'part' => 'snippet,contentDetails', - 'key' => $this->apiKey, - 'chart' => 'mostPopular', - 'maxResults' => $count + $value = \http_build_query([ + 'part' => 'snippet,contentDetails', + 'key' => $this->apiKey, + 'chart' => 'mostPopular', + 'maxResults' => $count, ]); - yield new Request( - 'GET', - 'https://www.googleapis.com/youtube/v3/videos?'.$value - ); + + yield new Request('GET', 'https://www.googleapis.com/youtube/v3/videos?' . $value); } - /** - * {@inheritdoc} - */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'youtube_video'; } diff --git a/src/YoutubePlaylistItemFeed.php b/src/YoutubePlaylistItemFeed.php index 843f76c..0e19a66 100644 --- a/src/YoutubePlaylistItemFeed.php +++ b/src/YoutubePlaylistItemFeed.php @@ -1,12 +1,13 @@ getFeedPlatform() . serialize($this->playlistId); + return $this->getFeedPlatform() . \serialize($this->playlistId); } - /** - * @param int $count - * - * @return \Generator - */ - public function getRequests($count = 5): \Generator + public function getRequests(int $count = 5): Generator { - $value = http_build_query([ - 'part' => 'snippet,contentDetails', - 'key' => $this->apiKey, + $value = \http_build_query([ + 'part' => 'snippet,contentDetails', + 'key' => $this->apiKey, 'playlistId' => $this->playlistId, 'maxResults' => $count, ]); yield new Request( 'GET', - 'https://www.googleapis.com/youtube/v3/playlistItems?'.$value + 'https://www.googleapis.com/youtube/v3/playlistItems?' . $value ); } /** * {@inheritdoc} */ - public function getFeedPlatform() + public function getFeedPlatform(): string { return 'youtube_playlist_items'; } @@ -64,7 +56,7 @@ public function getFeedPlatform() /** * @inheritDoc */ - protected function createFeedItemFromObject($item): FeedItem + protected function createFeedItemFromObject(stdClass $item): FeedItem { $feedItem = parent::createFeedItemFromObject($item); $feedItem->setId($item->snippet->resourceId->videoId); diff --git a/web/dev.php b/web/dev.php index c1fe5ee..cb3cabb 100644 --- a/web/dev.php +++ b/web/dev.php @@ -1,4 +1,5 @@ loadEnv(dirname(__DIR__) . '/.env'); +(new Dotenv())->usePutenv()->loadEnv(dirname(__DIR__).'/.env'); try { $sw = new Stopwatch(); @@ -28,13 +29,13 @@ $feedItems = $feed->getAsyncCanonicalItems((int) getenv('MF_FEED_LENGTH')); $event = $sw->stop('fetch'); $feedItemResponse = new FeedItemResponse($feedItems, [ - 'time' => $event->getDuration(), + 'time' => $event->getDuration(), 'memory' => $event->getMemory(), - 'count' => count($feedItems), + 'count' => count($feedItems), ]); echo $serializer->serialize($feedItemResponse, 'json'); } catch (\RuntimeException $exception) { - header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); + header($_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error', true, 500); echo json_encode([ 'message' => $exception->getMessage(), ]); diff --git a/web/index.php b/web/index.php index 4224465..4517166 100644 --- a/web/index.php +++ b/web/index.php @@ -1,4 +1,5 @@ loadEnv(dirname(__DIR__) . '/.env'); +(new Dotenv())->usePutenv()->loadEnv(dirname(__DIR__).'/.env'); try { $sw = new Stopwatch(); @@ -29,12 +30,12 @@ $feedItems = $feed->getAsyncCanonicalItems((int) getenv('MF_FEED_LENGTH')); $event = $sw->stop('fetch'); $feedItemResponse = new FeedItemResponse($feedItems, [ - 'time' => $event->getDuration(), + 'time' => $event->getDuration(), 'count' => count($feedItems), ]); echo $serializer->serialize($feedItemResponse, 'json'); } catch (\RuntimeException $exception) { - header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); + header($_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error', true, 500); echo json_encode([ 'message' => $exception->getMessage(), ]); diff --git a/web/test.sample.php b/web/test.sample.php index fc4d75f..2c38a3e 100644 --- a/web/test.sample.php +++ b/web/test.sample.php @@ -1,20 +1,19 @@ getAsyncCanonicalItems(20); $event = $sw->stop('fetch'); $feedItemResponse = new FeedItemResponse($feedItems, [ - 'time' => $event->getDuration(), + 'time' => $event->getDuration(), 'memory' => $event->getMemory(), ]); $jsonContent = $serializer->serialize($feedItemResponse, 'json');