diff --git a/migrations/Version20231119134901.php b/migrations/Version20231119134901.php new file mode 100644 index 0000000..f079778 --- /dev/null +++ b/migrations/Version20231119134901.php @@ -0,0 +1,29 @@ +addSql("INSERT INTO sources (id, name) VALUES ('eefdac30-454b-4455-99e8-cfe3a2bb6111','symfony.com')"); + } + + public function down(Schema $schema): void + { + $this->addSql("DELETE FROM sources WHERE id='eefdac30-454b-4455-99e8-cfe3a2bb6111'"); + } +} diff --git a/src/Feed/Application/Service/FeedProvider/SymfonyFeedProvider.php b/src/Feed/Application/Service/FeedProvider/SymfonyFeedProvider.php new file mode 100644 index 0000000..d8611fe --- /dev/null +++ b/src/Feed/Application/Service/FeedProvider/SymfonyFeedProvider.php @@ -0,0 +1,63 @@ +client->request('GET', 'https://feeds.feedburner.com/symfony/blog'); + + $feed = new DOMDocument(); + + $feed->loadXML($response->getContent()); + + $newsItems = []; + + foreach ($feed->getElementsByTagName('item') as $entry) { + try { + $title = DOM::getString($entry, 'title'); + $summary = trim(str_replace(["\r", "\n"], ' ', DOM::getString($entry, 'description'))); + $updated = DOM::getDateTime($entry, 'pubDate'); + $link = DOM::getString($entry, 'link'); + } catch (OutOfBoundsException | LogicException $exception) { + $this->logger->warning('[{source}] Failed to parse entry: {reason}.', [ + 'source' => self::getSource(), + 'reason' => $exception->getMessage(), + ]); + + continue; + } + + $feedItems = new FeedItem( + $title, + $summary, + $link, + $updated, + self::getSource() + ); + + $newsItems[] = $feedItems; + } + + return $newsItems; + } +} diff --git a/tests/Unit/Feed/Application/Service/FeedProvider/SymfonyFeedProviderTest.php b/tests/Unit/Feed/Application/Service/FeedProvider/SymfonyFeedProviderTest.php new file mode 100644 index 0000000..393fc1c --- /dev/null +++ b/tests/Unit/Feed/Application/Service/FeedProvider/SymfonyFeedProviderTest.php @@ -0,0 +1,208 @@ + + + + Symfony Blog + + https://symfony.com/blog/ + Most recent posts published on the Symfony project blog + Sun, 19 Nov 2023 21:44:09 +0100 + Sun, 19 Nov 2023 08:58:00 +0100 + en + + <![CDATA[SymfonyCon Brussels 2023: A Memorable Game UX with LiveComponents]]> + https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + +SymfonyCon Brussels 2023 is just around the corner and will start on: + + +December 5-6: Workshop days. It is possible to attend 1 two-day training or 2 one-day trainings! +December 7-8: Conference days with 3 parallels tracks and 1 unconference track… + + Sfconbrussels2023 Header +

+ +

SymfonyCon Brussels 2023 is just around the corner and will start on:

+ +
    +
  • December 5-6: Workshop days. It is possible to attend 1 two-day training or 2 one-day trainings!
  • +
  • December 7-8: Conference days with 3 parallels tracks and 1 unconference track in English.
  • +
+ +

🎤 New speaker announcement!

+ +

We are thrilled to announce you the next speaker: Simon André, Future developer, SensioLabs will present the topic "A Memorable Game UX with LiveComponents":

+ +

"Symfony UX empowers developers to create rich, dynamic web interfaces that bridge the gap between backend logic and a memorable user experience.

+
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Sat, 18 Nov 2023 15:30:00 +0100 + https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+ + <![CDATA[New in Symfony 6.4: Mailer, Translation, Notifier, Webhook and RemoteEvent Integrations]]> + https://symfony.com/blog/new-in-symfony-6-4-mailer-translation-notifier-webhook-and-remoteevent-integrations?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + Symfony provides out-of-the-box compatibility with tens of third-party services +for Mailer, Translation, Notifier, Webhook and RemoteEvent +components. In Symfony 6.4 we're introducing even more integrations: + +Mailer Integrations + + Scaleway transaction… + Symfony provides out-of-the-box compatibility with tens of third-party services +for Mailer, Translation, Notifier, Webhook and RemoteEvent +components. In Symfony 6.4 we're introducing even more integrations:

+
+

Mailer Integrations

+ +
]]]>
+ https://symfony.com/blog/new-in-symfony-6-4-autowirelocator-and-autowireiterator-attributes?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Thu, 16 Nov 2023 13:10:00 +0100 + https://symfony.com/blog/new-in-symfony-6-4-autowirelocator-and-autowireiterator-attributes?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+
+
+XML; + + private const MALFORMED_XML = << + + + Symfony Blog + + https://symfony.com/blog/ + Most recent posts published on the Symfony project blog + Sun, 19 Nov 2023 21:44:09 +0100 + Sun, 19 Nov 2023 08:58:00 +0100 + en + + <![CDATA[SymfonyCon Brussels 2023: A Memorable Game UX with LiveComponents]]> + https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Sat, 18 Nov 2023 15:30:00 +0100 + https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list + + + +XML; + + protected function setUp(): void + { + $this->logger = new InMemoryLogger(); + + parent::setUp(); + } + + /** + * @test + */ + public function it_should_fetch_the_external_feed(): void + { + // Arrange + $client = new MockHttpClient([ + new MockResponse(self::EXTERNAL_FEED) + ]); + + $feedProvider = new SymfonyFeedProvider($client, $this->logger); + + // Act + $feedItems = $feedProvider->fetchFeedItems(); + + // Assert + self::assertCount(2, $feedItems); + + self::assertSame("SymfonyCon Brussels 2023: A Memorable Game UX with LiveComponents", $feedItems[0]->title); + self::assertEquals( + "SymfonyCon Brussels 2023 is just around the corner and will start on: December 5-6: Workshop days. It is possible to attend 1 two-day training or 2 one-day trainings! December 7-8: Conference days with 3 parallels tracks and 1 unconference track…", + $feedItems[0]->summary + ); + self::assertSame( + "https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed", + $feedItems[0]->url + ); + self::assertEquals(\DateTime::createFromFormat('Y-m-d H:i', '2023-11-18 14:30'), $feedItems[0]->updated); + self::assertSame("symfony.com", $feedItems[0]->source); + + self::assertSame("New in Symfony 6.4: Mailer, Translation, Notifier, Webhook and RemoteEvent Integrations", $feedItems[1]->title); + self::assertEquals( + "Symfony provides out-of-the-box compatibility with tens of third-party services for Mailer, Translation, Notifier, Webhook and RemoteEvent components. In Symfony 6.4 we're introducing even more integrations: Mailer Integrations Scaleway transaction…", + $feedItems[1]->summary + ); + self::assertSame( + "https://symfony.com/blog/new-in-symfony-6-4-mailer-translation-notifier-webhook-and-remoteevent-integrations?utm_source=Symfony%20Blog%20Feed&utm_medium=feed", + $feedItems[1]->url + ); + self::assertEquals(\DateTime::createFromFormat('Y-m-d H:i', '2023-11-16 12:10'), $feedItems[1]->updated); + self::assertSame("symfony.com", $feedItems[1]->source); + } + + /** + * @test + */ + public function it_should_get_the_source_name(): void + { + // Assert + self::assertSame('symfony.com', SymfonyFeedProvider::getSource()); + } + + /** + * @test + */ + public function it_should_log_if_entry_cant_be_parsed(): void + { + // Arrange + $client = new MockHttpClient([ + new MockResponse(self::MALFORMED_XML) + ]); + + $feedProvider = new SymfonyFeedProvider($client, $this->logger); + + // Act + $feedItems = $feedProvider->fetchFeedItems(); + + // Assert + self::assertCount(1, $this->logger->recordedLogs); + + $log = $this->logger->recordedLogs[0]; + + self::assertSame(LogLevel::WARNING, $log->level); + self::assertSame('[{source}] Failed to parse entry: {reason}.', $log->message); + self::assertSame([ + 'source' => SymfonyFeedProvider::getSource(), + 'reason' => "`description` does not exist in DOMElement", + ], $log->context); + } +}