-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace DoctrineMigrations; | ||
|
||
use Doctrine\DBAL\Schema\Schema; | ||
use Doctrine\Migrations\AbstractMigration; | ||
|
||
/** | ||
* Auto-generated Migration: Please modify to your needs! | ||
*/ | ||
final class Version20231119134901 extends AbstractMigration | ||
{ | ||
public function getDescription(): string | ||
{ | ||
return 'Adds symfony source'; | ||
} | ||
|
||
public function up(Schema $schema): void | ||
{ | ||
$this->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'"); | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/Feed/Application/Service/FeedProvider/SymfonyFeedProvider.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
namespace App\Feed\Application\Service\FeedProvider; | ||
|
||
use App\Feed\Infrastructure\Helper\DOM\DOM; | ||
use DOMDocument; | ||
use LogicException; | ||
use OutOfBoundsException; | ||
use Psr\Log\LoggerInterface; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
|
||
final class SymfonyFeedProvider implements FeedProvider | ||
{ | ||
public function __construct( | ||
private HttpClientInterface $client, | ||
private LoggerInterface $logger, | ||
) { | ||
} | ||
|
||
public static function getSource(): string | ||
{ | ||
return 'symfony.com'; | ||
} | ||
|
||
public function fetchFeedItems(): array | ||
{ | ||
$response = $this->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; | ||
} | ||
} |
208 changes: 208 additions & 0 deletions
208
tests/Unit/Feed/Application/Service/FeedProvider/SymfonyFeedProviderTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
<?php | ||
|
||
namespace Unit\Feed\Application\Service\FeedProvider; | ||
|
||
use App\Feed\Application\Service\FeedProvider\MartinFowlerFeedProvider; | ||
use App\Feed\Application\Service\FeedProvider\StitcherFeedProvider; | ||
use App\Feed\Application\Service\FeedProvider\SymfonyFeedProvider; | ||
use Dev\Common\Infrastructure\Logger\InMemoryLogger; | ||
use PHPUnit\Framework\TestCase; | ||
use Psr\Log\LogLevel; | ||
use Symfony\Component\HttpClient\MockHttpClient; | ||
use Symfony\Component\HttpClient\Response\MockResponse; | ||
|
||
class SymfonyFeedProviderTest extends TestCase | ||
{ | ||
private InMemoryLogger $logger; | ||
|
||
private const EXTERNAL_FEED = <<<XML | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"> | ||
<channel> | ||
<title>Symfony Blog</title> | ||
<atom:link href="https://feeds.feedburner.com/symfony/blog" rel="self" type="application/rss+xml" /> | ||
<link>https://symfony.com/blog/</link> | ||
<description>Most recent posts published on the Symfony project blog</description> | ||
<pubDate>Sun, 19 Nov 2023 21:44:09 +0100</pubDate> | ||
<lastBuildDate>Sun, 19 Nov 2023 08:58:00 +0100</lastBuildDate> | ||
<language>en</language> | ||
<item> | ||
<title><![CDATA[SymfonyCon Brussels 2023: A Memorable Game UX with LiveComponents]]></title> | ||
<link>https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link> | ||
<description> | ||
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…</description> | ||
<content:encoded><![CDATA[ | ||
<p><a class="block text-center" href="https://live.symfony.com/2023-brussels-con" title="Sfconbrussels2023 Header"> | ||
<img src="https://symfony.com/uploads/assets/blog/sfconbrussels2023-header.jpg" alt="Sfconbrussels2023 Header"> | ||
</a></p> | ||
<p><strong>SymfonyCon Brussels 2023</strong> is just around the corner and will start on:</p> | ||
<ul> | ||
<li><strong>December 5-6:</strong> Workshop days. It is possible to attend 1 two-day training or 2 one-day trainings!</li> | ||
<li><strong>December 7-8:</strong> Conference days with 3 parallels tracks and 1 unconference track in English.</li> | ||
</ul> | ||
<h2>🎤 New speaker announcement!</h2> | ||
<p>We are thrilled to announce you the next speaker: <strong><a href="https://connect.symfony.com/profile/simonandre">Simon André</a></strong>, Future developer, SensioLabs will present the topic <strong><a href="https://live.symfony.com/2023-brussels-con/schedule/a-memorable-game-ux-with-livecomponents">"A Memorable Game UX with LiveComponents"</a></strong>:</p> | ||
<p>"Symfony UX empowers developers to create rich, dynamic web interfaces that bridge the gap between backend logic and a memorable user experience.</p> | ||
<hr style="margin-bottom: 5px" /> | ||
<div style="font-size: 90%"> | ||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project. | ||
</div> | ||
]]></content:encoded> | ||
<guid isPermaLink="false">https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid> | ||
<dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator> | ||
<pubDate>Sat, 18 Nov 2023 15:30:00 +0100</pubDate> | ||
<comments>https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments> | ||
</item> | ||
<item> | ||
<title><![CDATA[New in Symfony 6.4: Mailer, Translation, Notifier, Webhook and RemoteEvent Integrations]]></title> | ||
<link>https://symfony.com/blog/new-in-symfony-6-4-mailer-translation-notifier-webhook-and-remoteevent-integrations?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link> | ||
<description>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…</description> | ||
<content:encoded><![CDATA[ | ||
<p>Symfony provides out-of-the-box compatibility with tens of third-party services | ||
for <a href="https://symfony.com/mailer" class="reference external">Mailer</a>, <a href="https://symfony.com/translation" class="reference external">Translation</a>, <a href="https://symfony.com/notifier" class="reference external">Notifier</a>, <a href="https://symfony.com/web-hook" class="reference external">Webhook</a> and <a href="https://symfony.com/remote-event" class="reference external">RemoteEvent</a> | ||
components. In Symfony 6.4 we're introducing even more integrations:</p> | ||
<div class="section"> | ||
<h2 id="mailer-integrations"><a class="headerlink" href="#mailer-integrations" title="Permalink to this headline">Mailer Integrations</a></h2> | ||
<ul> | ||
<li><a href="https://www.scaleway.com/en/transactional-email-tem/" class="reference external" rel="external noopener noreferrer" target="_blank">Scaleway</a> transaction | ||
email integration added by <a href="https://github.com/MrMicky-FR" class="reference external" rel="external noopener noreferrer" target="_blank">MrMicky</a> in | ||
<a href="https://github.com/symfony/symfony/pull/51014" class="reference external" rel="external noopener noreferrer" target="_blank">PR #51014</a>;</li> | ||
<li><a href="https://developers.brevo.com/" class="reference external" rel="external noopener noreferrer" target="_blank">Brevo</a> integration added by | ||
<a href="https://github.com/PEtanguy" class="reference external" rel="external noopener noreferrer" target="_blank">Pierre Tanguy</a> in | ||
<a href="https://github.com/symfony/symfony/pull/50302" class="reference external" rel="external noopener noreferrer" target="_blank">PR #50302</a>.</li> | ||
</ul> | ||
</div>]]]></content:encoded> | ||
<guid isPermaLink="false">https://symfony.com/blog/new-in-symfony-6-4-autowirelocator-and-autowireiterator-attributes?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid> | ||
<dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator> | ||
<pubDate>Thu, 16 Nov 2023 13:10:00 +0100</pubDate> | ||
<comments>https://symfony.com/blog/new-in-symfony-6-4-autowirelocator-and-autowireiterator-attributes?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments> | ||
</item> | ||
</channel> | ||
</rss> | ||
XML; | ||
|
||
private const MALFORMED_XML = <<<XML | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"> | ||
<channel> | ||
<title>Symfony Blog</title> | ||
<atom:link href="https://feeds.feedburner.com/symfony/blog" rel="self" type="application/rss+xml" /> | ||
<link>https://symfony.com/blog/</link> | ||
<description>Most recent posts published on the Symfony project blog</description> | ||
<pubDate>Sun, 19 Nov 2023 21:44:09 +0100</pubDate> | ||
<lastBuildDate>Sun, 19 Nov 2023 08:58:00 +0100</lastBuildDate> | ||
<language>en</language> | ||
<item> | ||
<title><![CDATA[SymfonyCon Brussels 2023: A Memorable Game UX with LiveComponents]]></title> | ||
<link>https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link> | ||
<guid isPermaLink="false">https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid> | ||
<dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator> | ||
<pubDate>Sat, 18 Nov 2023 15:30:00 +0100</pubDate> | ||
<comments>https://symfony.com/blog/symfonycon-brussels-2023-a-memorable-game-ux-with-livecomponents?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments> | ||
</item> | ||
</channel> | ||
</rss> | ||
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); | ||
} | ||
} |