diff --git a/demo/config/packages/ai.yaml b/demo/config/packages/ai.yaml
index eed1da860..effbeb9a7 100644
--- a/demo/config/packages/ai.yaml
+++ b/demo/config/packages/ai.yaml
@@ -60,7 +60,7 @@ ai:
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Embeddings::TEXT_ADA_002
indexer:
blog:
- loader: 'App\Blog\FeedLoader'
+ loader: 'Symfony\AI\Store\Document\Loader\RssFeedLoader'
source: 'https://feeds.feedburner.com/symfony/blog'
transformers:
- 'Symfony\AI\Store\Document\Transformer\TextTrimTransformer'
@@ -81,4 +81,5 @@ services:
$vectorizer: '@ai.vectorizer.openai'
$store: '@ai.store.chroma_db.symfonycon'
+ Symfony\AI\Store\Document\Loader\RssFeedLoader: ~
Symfony\AI\Store\Document\Transformer\TextTrimTransformer: ~
diff --git a/demo/src/Blog/FeedLoader.php b/demo/src/Blog/FeedLoader.php
deleted file mode 100644
index 66a763aad..000000000
--- a/demo/src/Blog/FeedLoader.php
+++ /dev/null
@@ -1,68 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace App\Blog;
-
-use Symfony\AI\Store\Document\LoaderInterface;
-use Symfony\AI\Store\Document\Metadata;
-use Symfony\AI\Store\Document\TextDocument;
-use Symfony\AI\Store\Exception\InvalidArgumentException;
-use Symfony\Component\DomCrawler\Crawler;
-use Symfony\Component\Uid\Uuid;
-use Symfony\Contracts\HttpClient\HttpClientInterface;
-
-final class FeedLoader implements LoaderInterface
-{
- public function __construct(
- private HttpClientInterface $httpClient,
- ) {
- }
-
- /**
- * @param ?string $source RSS feed URL
- * @param array $options
- *
- * @return iterable
- */
- public function load(?string $source, array $options = []): iterable
- {
- if (null === $source) {
- throw new InvalidArgumentException('FeedLoader requires a RSS feed URL as source, null given.');
- }
- $result = $this->httpClient->request('GET', $source);
-
- $posts = [];
- $crawler = new Crawler($result->getContent());
- $crawler->filter('item')->each(function (Crawler $node) use (&$posts) {
- $title = $node->filter('title')->text() ?: '';
- $link = $node->filter('link')->text() ?: '';
- $description = $node->filter('description')->text() ?: '';
- $contentEncoded = $node->filter('content\:encoded')->text();
- $content = $contentEncoded ? (new Crawler($contentEncoded))->text() : '';
- $author = $node->filter('dc\:creator')->text() ?: '';
- $pubDate = $node->filter('pubDate')->text();
-
- $posts[] = new Post(
- Uuid::v5(Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'), $title),
- $title,
- $link,
- $description,
- $content ?: '',
- $author,
- new \DateTimeImmutable($pubDate),
- );
- });
-
- foreach ($posts as $post) {
- yield new TextDocument($post->id, $post->toString(), new Metadata($post->toArray()));
- }
- }
-}
diff --git a/demo/tests/Blog/FeedLoaderTest.php b/demo/tests/Blog/FeedLoaderTest.php
deleted file mode 100644
index de713f4fc..000000000
--- a/demo/tests/Blog/FeedLoaderTest.php
+++ /dev/null
@@ -1,160 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace App\Tests\Blog;
-
-use App\Blog\FeedLoader;
-use App\Blog\Post;
-use PHPUnit\Framework\Attributes\CoversClass;
-use PHPUnit\Framework\Attributes\UsesClass;
-use PHPUnit\Framework\TestCase;
-use Symfony\AI\Store\Document\Metadata;
-use Symfony\AI\Store\Document\TextDocument;
-use Symfony\AI\Store\Exception\InvalidArgumentException;
-use Symfony\Component\HttpClient\Exception\ClientException;
-use Symfony\Component\HttpClient\MockHttpClient;
-use Symfony\Component\HttpClient\Response\MockResponse;
-use Symfony\Component\Uid\Uuid;
-
-#[CoversClass(FeedLoader::class)]
-#[UsesClass(Post::class)]
-final class FeedLoaderTest extends TestCase
-{
- public function testLoadWithValidFeedUrl()
- {
- $loader = new FeedLoader(new MockHttpClient(MockResponse::fromFile(__DIR__.'/../fixtures/symfony-blog.rss')));
- $documents = iterator_to_array($loader->load('https://feeds.feedburner.com/symfony/blog'));
-
- $this->assertCount(2, $documents);
-
- // Test first document
- $firstDocument = $documents[0];
- $this->assertInstanceOf(TextDocument::class, $firstDocument);
-
- $expectedFirstUuid = Uuid::v5(Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'), 'Save the date, SymfonyDay Montreal 2026!');
- $this->assertEquals($expectedFirstUuid, $firstDocument->id);
-
- $this->assertStringContainsString('Title: Save the date, SymfonyDay Montreal 2026!', $firstDocument->content);
- $this->assertStringContainsString('From: Paola Suárez on 2025-09-11', $firstDocument->content);
- $this->assertStringContainsString("We're thrilled to announce that SymfonyDay Montreal is happening on June 4, 2026!", $firstDocument->content);
- $this->assertStringContainsString('Mark your calendars, tell your friends', $firstDocument->content);
-
- $firstMetadata = $firstDocument->metadata;
- $this->assertSame($expectedFirstUuid->toRfc4122(), $firstMetadata['id']);
- $this->assertSame('Save the date, SymfonyDay Montreal 2026!', $firstMetadata['title']);
- $this->assertSame('https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed', $firstMetadata['link']);
- $this->assertStringContainsString("We're thrilled to announce that SymfonyDay Montreal is happening on June 4, 2026!", $firstMetadata['description']);
- $this->assertStringContainsString('Mark your calendars, tell your friends', $firstMetadata['content']);
- $this->assertSame('Paola Suárez', $firstMetadata['author']);
- $this->assertSame('2025-09-11', $firstMetadata['date']);
-
- // Test second document
- $secondDocument = $documents[1];
- $this->assertInstanceOf(TextDocument::class, $secondDocument);
-
- $expectedSecondUuid = Uuid::v5(Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'), 'SymfonyCon Amsterdam 2025: Call for IT student volunteers: Volunteer, Learn & Connect!');
- $this->assertEquals($expectedSecondUuid, $secondDocument->id);
-
- $this->assertStringContainsString('Title: SymfonyCon Amsterdam 2025: Call for IT student volunteers: Volunteer, Learn & Connect!', $secondDocument->content);
- $this->assertStringContainsString('From: Paola Suárez on 2025-09-10', $secondDocument->content);
- $this->assertStringContainsString('🎓SymfonyCon Amsterdam 2025: Call for IT Student Volunteers!', $secondDocument->content);
-
- $secondMetadata = $secondDocument->metadata;
- $this->assertSame($expectedSecondUuid->toRfc4122(), $secondMetadata['id']);
- $this->assertSame('SymfonyCon Amsterdam 2025: Call for IT student volunteers: Volunteer, Learn & Connect!', $secondMetadata['title']);
- $this->assertSame('https://symfony.com/blog/symfonycon-amsterdam-2025-call-for-it-student-volunteers-volunteer-learn-and-connect?utm_source=Symfony%20Blog%20Feed&utm_medium=feed', $secondMetadata['link']);
- $this->assertStringContainsString('🎓SymfonyCon Amsterdam 2025: Call for IT Student Volunteers!', $secondMetadata['description']);
- $this->assertStringContainsString('🎓SymfonyCon Amsterdam 2025: Call for IT Student Volunteers!', $secondMetadata['content']);
- $this->assertSame('Paola Suárez', $secondMetadata['author']);
- $this->assertSame('2025-09-10', $secondMetadata['date']);
- }
-
- public function testLoadWithNullSource()
- {
- $loader = new FeedLoader(new MockHttpClient([]));
-
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('FeedLoader requires a RSS feed URL as source, null given.');
-
- iterator_to_array($loader->load(null));
- }
-
- public function testLoadWithEmptyFeed()
- {
- $emptyFeedXml = <<
-
-
- Empty Feed
- https://example.com/
- An empty RSS feed
-
-
-XML;
-
- $loader = new FeedLoader(new MockHttpClient(new MockResponse($emptyFeedXml)));
- $documents = iterator_to_array($loader->load('https://example.com/feed.xml'));
-
- $this->assertCount(0, $documents);
- }
-
- public function testLoadWithHttpError()
- {
- $loader = new FeedLoader(new MockHttpClient(new MockResponse('', ['http_code' => 404])));
-
- $this->expectException(ClientException::class);
-
- iterator_to_array($loader->load('https://example.com/non-existent-feed.xml'));
- }
-
- public function testLoadWithMalformedXml()
- {
- $malformedXml = 'Not XML at all';
-
- $loader = new FeedLoader(new MockHttpClient(new MockResponse($malformedXml)));
- $documents = iterator_to_array($loader->load('https://example.com/malformed-feed.xml'));
-
- $this->assertCount(0, $documents);
- }
-
- public function testLoadReturnsIterableOfTextDocuments()
- {
- $loader = new FeedLoader(new MockHttpClient(MockResponse::fromFile(__DIR__.'/../fixtures/symfony-blog.rss')));
- $result = $loader->load('https://feeds.feedburner.com/symfony/blog');
-
- $this->assertIsIterable($result);
-
- foreach ($result as $document) {
- $this->assertInstanceOf(TextDocument::class, $document);
- $this->assertInstanceOf(Uuid::class, $document->id);
- $this->assertIsString($document->content);
- $this->assertNotEmpty($document->content);
- $this->assertInstanceOf(Metadata::class, $document->metadata);
- }
- }
-
- public function testLoadGeneratesConsistentUuids()
- {
- $loader = new FeedLoader(new MockHttpClient(MockResponse::fromFile(__DIR__.'/../fixtures/symfony-blog.rss')));
- $documents1 = iterator_to_array($loader->load('https://feeds.feedburner.com/symfony/blog'));
-
- // Load same feed again
- $loader2 = new FeedLoader(new MockHttpClient(MockResponse::fromFile(__DIR__.'/../fixtures/symfony-blog.rss')));
- $documents2 = iterator_to_array($loader2->load('https://feeds.feedburner.com/symfony/blog'));
-
- $this->assertCount(2, $documents1);
- $this->assertCount(2, $documents2);
-
- // UUIDs should be identical for same content
- $this->assertEquals($documents1[0]->id, $documents2[0]->id);
- $this->assertEquals($documents1[1]->id, $documents2[1]->id);
- }
-}
diff --git a/demo/tests/fixtures/symfony-blog.rss b/demo/tests/fixtures/symfony-blog.rss
deleted file mode 100644
index d20a27f00..000000000
--- a/demo/tests/fixtures/symfony-blog.rss
+++ /dev/null
@@ -1,175 +0,0 @@
-
-
-
- Symfony Blog
-
- https://symfony.com/blog/
- Most recent posts published on the Symfony project blog
- Fri, 12 Sep 2025 14:25:38 +0200
- Thu, 11 Sep 2025 14:30:00 +0200
- en
-
-
- https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
-
-
-
-
-We're thrilled to announce that SymfonyDay Montreal is happening on June 4, 2026! 🎉
-
-Mark your calendars, tell your friends, and get ready for a day full of inspiring talks, networking opportunities, and the vibrant energy of the Symfony community…
-
-
-
-
-
We're thrilled to announce that SymfonyDay Montreal is happening on June 4, 2026! 🎉
-
-
Mark your calendars, tell your friends, and get ready for a day full of inspiring talks, networking opportunities, and the vibrant energy of the Symfony community in Canada! 🍁
Submit your talk for SymfonyDay Montreal 2026! Every selected speaker will receive a complimentary conference ticket and a speaker gift! Speakers who do not live in the conference city will also have their travel and accommodation expenses covered.
-
-
If you have never spoken at a conference before, you can take advantage of our speaker mentoring program! Experienced speakers will gladly assist you in preparing for your conference, whether it involves creating your slides or rehearsing your talk. Feel free to seek advice in the #diversity or #speaker-mentoring channels on the Symfony Devs Slack or include comments requesting guidance when submitting your talk proposal.
-
-
-
-
🤝 Want to support the event?
-
-
Become a sponsor and showcase your brand to an engaged audience of developers.
-
-
⚡Contact Hadrien Cren by email to learn more about the options.
- ]]>
- https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
-
- Thu, 11 Sep 2025 14:30:00 +0200
- https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
-
-
-
- https://symfony.com/blog/symfonycon-amsterdam-2025-call-for-it-student-volunteers-volunteer-learn-and-connect?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
-
-
-
-
-🎓SymfonyCon Amsterdam 2025: Call for IT Student Volunteers!
-
-Are you an IT or computer science student looking to gain hands-on experience, grow your network, and take part in an international tech event? 🎤✨
-Symfony is looking for motivated student…
-
-
-
-
-
🎓SymfonyCon Amsterdam 2025: Call for IT Student Volunteers!
-
-
Are you an IT or computer science student looking to gain hands-on experience, grow your network, and take part in an international tech event? 🎤✨
-Symfony is looking for motivated student volunteers to join the adventure at SymfonyCon Amsterdam 2025!
-
-
📍Where: Amsterdam, The Netherlands
-
-
🗓️When: November 28–29, 2025
-
-
Volunteering with us means helping out for one day and getting a free ticket to attend the conference the other day, so you don't miss out on the action!
-
-
-
-
💼 Volunteer Tasks
-
-
As a volunteer, you'll be assigned to:
-
-
✔️Welcome attendees
-
-
✔️Provide directions and information
-
-
✔️Help ensure everything runs smoothly!
-
-
-
-
✅ Requirements
-
-
To apply, you must:
-
-
✔️Be a student in computer science / IT (all backgrounds welcome!)
-
-
✔️Be curious about careers in development or tech
-
-
✔️Be available for at least one full day during the event
-
-
✔️Speak English (conference language)
-
-
-
-
🎁 What You Get
-
-
Free access to the full 2-day SymfonyCon Amsterdam 2025 conference
-
-
✔️Opportunities to network with developers, speakers, and community leaders
-
-
✔️Delicious lunches included
-
-
✔️An unforgettable experience in an inclusive and friendly environment!
-
-
-
-
🚫 Please note:
-
-
We are unable to cover:
-
-
✔️Travel or accommodation costs ✔️Dinners or evening expenses
-
-
-
-
💌 How to Apply:
-
-
Interested? Send us an email at events@symfony.com with:
-
-
✔️Your name, school, and area of study
-
-
✔️The reason you'd like to volunteer
-
-
✔️Confirmation of your availability on November 28 or 29, 2025
-
-
-
-
Be part of the Symfony community, gain valuable experience, and help make this event amazing!
-We can't wait to meet you! 🧑💻💬 #SymfonyCon
- ]]>
- https://symfony.com/blog/symfonycon-amsterdam-2025-call-for-it-student-volunteers-volunteer-learn-and-connect?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
-
- Wed, 10 Sep 2025 09:00:00 +0200
- https://symfony.com/blog/symfonycon-amsterdam-2025-call-for-it-student-volunteers-volunteer-learn-and-connect?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
-
-
-
diff --git a/examples/indexer/index-rss-loader.php b/examples/indexer/index-rss-loader.php
new file mode 100644
index 000000000..e42b013c8
--- /dev/null
+++ b/examples/indexer/index-rss-loader.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\AI\Platform\Bridge\OpenAi\Embeddings;
+use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
+use Symfony\AI\Store\Bridge\Local\InMemoryStore;
+use Symfony\AI\Store\Document\Loader\RssFeedLoader;
+use Symfony\AI\Store\Document\Transformer\TextSplitTransformer;
+use Symfony\AI\Store\Document\Vectorizer;
+use Symfony\AI\Store\Indexer;
+use Symfony\Component\HttpClient\HttpClient;
+
+require_once dirname(__DIR__).'/bootstrap.php';
+
+$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
+$store = new InMemoryStore();
+$vectorizer = new Vectorizer($platform, new Embeddings('text-embedding-3-small'));
+$indexer = new Indexer(
+ loader: new RssFeedLoader(HttpClient::create()),
+ vectorizer: $vectorizer,
+ store: $store,
+ source: [
+ 'https://feeds.feedburner.com/symfony/blog',
+ 'https://www.tagesschau.de/index~rss2.xml',
+ ],
+ transformers: [
+ new TextSplitTransformer(chunkSize: 500, overlap: 100),
+ ],
+);
+
+$indexer->index();
+
+$vector = $vectorizer->vectorize('Week of Symfony');
+$results = $store->query($vector);
+foreach ($results as $i => $document) {
+ echo sprintf("%d. %s\n", $i + 1, substr($document->id, 0, 40).'...');
+}
diff --git a/src/store/composer.json b/src/store/composer.json
index 74893f7a2..628c04186 100644
--- a/src/store/composer.json
+++ b/src/store/composer.json
@@ -49,7 +49,8 @@
"probots-io/pinecone-php": "^1.0",
"symfony/cache": "^6.4 || ^7.1",
"symfony/console": "^6.4 || ^7.1",
- "symfony/dependency-injection": "^6.4 || ^7.1"
+ "symfony/dependency-injection": "^6.4 || ^7.1",
+ "symfony/dom-crawler": "^6.4 || ^7.1"
},
"conflict": {
"mongodb/mongodb": "<1.21"
diff --git a/src/store/src/Document/Loader/Rss/RssItem.php b/src/store/src/Document/Loader/Rss/RssItem.php
new file mode 100644
index 000000000..9fabd03c8
--- /dev/null
+++ b/src/store/src/Document/Loader/Rss/RssItem.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\AI\Store\Document\Loader\Rss;
+
+use Symfony\Component\Uid\Uuid;
+
+/**
+ * @author Niklas Grießer
+ */
+final readonly class RssItem
+{
+ public function __construct(
+ public Uuid $id,
+ public string $title,
+ public string $link,
+ public \DateTimeImmutable $date,
+ public string $description,
+ public ?string $author,
+ public ?string $content,
+ ) {
+ }
+
+ public function toString(): string
+ {
+ return trim(<<title}
+Date: {$this->date->format('Y-m-d H:i')}
+Link: {$this->link}
+Description: {$this->description}
+
+{$this->content}
+EOD);
+ }
+
+ /**
+ * @return array{
+ * id: string,
+ * title: string,
+ * date: string,
+ * link: string,
+ * author: string,
+ * description: string,
+ * content: string,
+ * }
+ */
+ public function toArray(): array
+ {
+ return [
+ 'id' => $this->id->toRfc4122(),
+ 'title' => $this->title,
+ 'date' => $this->date->format('Y-m-d H:i'),
+ 'link' => $this->link,
+ 'author' => $this->author,
+ 'description' => $this->description,
+ 'content' => $this->content,
+ ];
+ }
+}
diff --git a/src/store/src/Document/Loader/RssFeedLoader.php b/src/store/src/Document/Loader/RssFeedLoader.php
new file mode 100644
index 000000000..6c93ec599
--- /dev/null
+++ b/src/store/src/Document/Loader/RssFeedLoader.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\AI\Store\Document\Loader;
+
+use Symfony\AI\Store\Document\Loader\Rss\RssItem;
+use Symfony\AI\Store\Document\LoaderInterface;
+use Symfony\AI\Store\Document\Metadata;
+use Symfony\AI\Store\Document\TextDocument;
+use Symfony\AI\Store\Exception\InvalidArgumentException;
+use Symfony\AI\Store\Exception\RuntimeException;
+use Symfony\Component\DomCrawler\Crawler;
+use Symfony\Component\Uid\Uuid;
+use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * @author Niklas Grießer
+ */
+final readonly class RssFeedLoader implements LoaderInterface
+{
+ public const OPTION_UUID_NAMESPACE = 'uuid_namespace';
+
+ /**
+ * @param string $uuidNamespace The namespace used to generate stable identifiers using UUIDv5
+ */
+ public function __construct(
+ private HttpClientInterface $httpClient,
+ private string $uuidNamespace = '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
+ ) {
+ }
+
+ /**
+ * @param array{uuid_namespace?: string} $options
+ */
+ public function load(?string $source, array $options = []): iterable
+ {
+ if (!class_exists(Crawler::class)) {
+ throw new RuntimeException('For using the RSS loader, the Symfony DomCrawler component is required. Try running "composer require symfony/dom-crawler".');
+ }
+
+ if (null === $source) {
+ throw new InvalidArgumentException(\sprintf('"%s" requires a URL as source, null given.', self::class));
+ }
+
+ $uuidNamespace = Uuid::fromString($options[self::OPTION_UUID_NAMESPACE] ?? $this->uuidNamespace);
+
+ try {
+ $xml = $this->httpClient->request('GET', $source, [
+ 'headers' => [
+ 'Accept' => 'application/rss+xml,application/xml,text/xml',
+ ],
+ ])->getContent();
+ } catch (ExceptionInterface $exception) {
+ throw new RuntimeException(\sprintf('Failed to fetch RSS feed from "%s": "%s"', $source, $exception->getMessage()), previous: $exception);
+ }
+
+ $crawler = new Crawler($xml);
+ foreach ($crawler->filterXpath('rss/channel/item') as $item) {
+ $node = new Crawler($item);
+ $guid = $node->filterXpath('node()/guid')->count() > 0 ? $node->filterXpath('node()/guid')->text() : null;
+ $link = $node->filterXpath('node()/link')->text();
+ $id = null !== $guid && Uuid::isValid($guid) ? Uuid::fromString($guid) : Uuid::v5($uuidNamespace, $guid ?? $link);
+ $author = $node->filterXpath('node()/dc:creator')->count() > 0 ? $node->filterXpath('node()/dc:creator')->text() : null;
+ $content = $node->filterXpath('node()/content:encoded')->count() > 0 ? $node->filterXpath('node()/content:encoded')->text() : null;
+
+ $item = new RssItem($id, $node->filterXpath('//title')->text(), $link, new \DateTimeImmutable($node->filterXpath('//pubDate')->text()), $node->filterXpath('//description')->text(), $author, $content);
+
+ yield new TextDocument($id, $item->toString(), new Metadata([
+ Metadata::KEY_SOURCE => $source,
+ ...$item->toArray(),
+ ]));
+ }
+ }
+}
diff --git a/src/store/tests/Document/Loader/RssFeedLoaderTest.php b/src/store/tests/Document/Loader/RssFeedLoaderTest.php
new file mode 100644
index 000000000..4269c1e39
--- /dev/null
+++ b/src/store/tests/Document/Loader/RssFeedLoaderTest.php
@@ -0,0 +1,154 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\AI\Store\Tests\Document\Loader;
+
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\TestCase;
+use Symfony\AI\Store\Document\Loader\RssFeedLoader;
+use Symfony\AI\Store\Document\TextDocument;
+use Symfony\AI\Store\Exception\InvalidArgumentException;
+use Symfony\AI\Store\Exception\RuntimeException;
+use Symfony\Component\HttpClient\MockHttpClient;
+use Symfony\Component\HttpClient\Response\MockResponse;
+use Symfony\Component\Uid\Uuid;
+
+/**
+ * @author Niklas Grießer
+ */
+#[CoversClass(RssFeedLoader::class)]
+final class RssFeedLoaderTest extends TestCase
+{
+ public function testLoadWithNullSource()
+ {
+ $httpClient = new MockHttpClient([]);
+ $loader = new RssFeedLoader($httpClient);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage(\sprintf('"%s" requires a URL as source, null given.', RssFeedLoader::class));
+
+ iterator_to_array($loader->load(null));
+ }
+
+ public function testLoadWithValidRssFeed()
+ {
+ $httpClient = new MockHttpClient([new MockResponse(file_get_contents(__DIR__.'/../../fixtures/symfony-blog.rss'))]);
+ $loader = new RssFeedLoader($httpClient);
+
+ $documents = iterator_to_array($loader->load('https://feeds.feedburner.com/symfony/blog'));
+ $this->assertCount(10, $documents);
+
+ // Test first document
+ $firstDocument = $documents[0];
+ $this->assertInstanceOf(TextDocument::class, $firstDocument);
+ $this->assertStringStartsWith('Title: Save the date, SymfonyDay Montreal 2026!', $firstDocument->content);
+ $this->assertStringContainsString('Date: 2025-09-11 14:30', $firstDocument->content);
+ $this->assertStringContainsString('SymfonyDay Montreal is happening on', $firstDocument->content);
+
+ $firstMetadata = $firstDocument->metadata;
+ $this->assertSame('Save the date, SymfonyDay Montreal 2026!', $firstMetadata['title']);
+ $this->assertSame('https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed', $firstMetadata['link']);
+ $this->assertSame('Paola Suárez', $firstMetadata['author']);
+ $this->assertSame('2025-09-11 14:30', $firstMetadata['date']);
+ }
+
+ public function testLoadWithInvalidRssFeed()
+ {
+ $httpClient = new MockHttpClient([new MockResponse('not XML at all')]);
+ $loader = new RssFeedLoader($httpClient);
+
+ $documents = iterator_to_array($loader->load('https://feeds.feedburner.com/symfony/blog'));
+ $this->assertCount(0, $documents);
+ }
+
+ public function testLoadWithHttpError()
+ {
+ $httpClient = new MockHttpClient([new MockResponse('Page not found', ['http_code' => 404])]);
+ $loader = new RssFeedLoader($httpClient);
+
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessageMatches('/Failed to fetch RSS feed/');
+
+ iterator_to_array($loader->load('https://feeds.feedburner.com/symfony/blog'));
+ }
+
+ public function testLoadWithEmptyFeed()
+ {
+ $emptyFeedXml = <<
+
+
+ Empty Feed
+ https://example.com/
+ An empty RSS feed
+
+
+XML;
+
+ $httpClient = new MockHttpClient([new MockResponse($emptyFeedXml)]);
+ $loader = new RssFeedLoader($httpClient);
+
+ $documents = iterator_to_array($loader->load('https://example.com/feed.xml'));
+ $this->assertCount(0, $documents);
+ }
+
+ public function testLoadWithHttpClientException()
+ {
+ $httpClient = new MockHttpClient([new MockResponse('', ['http_code' => 404])]);
+ $loader = new RssFeedLoader($httpClient);
+
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessageMatches('/Failed to fetch RSS feed/');
+
+ iterator_to_array($loader->load('https://example.com/non-existent-feed.xml'));
+ }
+
+ public function testLoadWithMalformedXml()
+ {
+ $httpClient = new MockHttpClient([new MockResponse('Not XML at all')]);
+ $loader = new RssFeedLoader($httpClient);
+
+ $documents = iterator_to_array($loader->load('https://example.com/malformed-feed.xml'));
+ $this->assertCount(0, $documents);
+ }
+
+ public function testLoadReturnsIterableOfTextDocuments()
+ {
+ $httpClient = new MockHttpClient([new MockResponse(file_get_contents(__DIR__.'/../../fixtures/symfony-blog.rss'))]);
+ $loader = new RssFeedLoader($httpClient);
+ $result = $loader->load('https://feeds.feedburner.com/symfony/blog');
+
+ foreach ($result as $document) {
+ $this->assertInstanceOf(TextDocument::class, $document);
+ $this->assertInstanceOf(Uuid::class, $document->id);
+ $this->assertNotEmpty($document->content);
+ }
+ }
+
+ public function testLoadGeneratesConsistentUuids()
+ {
+ $httpClient = new MockHttpClient([new MockResponse(file_get_contents(__DIR__.'/../../fixtures/symfony-blog.rss'))]);
+ $loader = new RssFeedLoader($httpClient);
+ $documents1 = iterator_to_array($loader->load('https://feeds.feedburner.com/symfony/blog'));
+
+ // Load same feed again
+ $httpClient2 = new MockHttpClient([new MockResponse(file_get_contents(__DIR__.'/../../fixtures/symfony-blog.rss'))]);
+ $loader2 = new RssFeedLoader($httpClient2);
+ $documents2 = iterator_to_array($loader2->load('https://feeds.feedburner.com/symfony/blog'));
+
+ $this->assertCount(10, $documents1);
+ $this->assertCount(10, $documents2);
+
+ // UUIDs should be identical for same content
+ $this->assertEquals($documents1[0]->id, $documents2[0]->id);
+ $this->assertEquals($documents1[1]->id, $documents2[1]->id);
+ }
+}
diff --git a/src/store/tests/fixtures/symfony-blog.rss b/src/store/tests/fixtures/symfony-blog.rss
new file mode 100644
index 000000000..a9b142852
--- /dev/null
+++ b/src/store/tests/fixtures/symfony-blog.rss
@@ -0,0 +1,828 @@
+
+
+
+ Symfony Blog
+
+ https://symfony.com/blog/
+ Most recent posts published on the Symfony project blog
+ Fri, 12 Sep 2025 14:25:38 +0200
+ Thu, 11 Sep 2025 14:30:00 +0200
+ en
+
+
+ https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+
+
+
+We’re thrilled to announce that SymfonyDay Montreal is happening on June 4, 2026! 🎉
+
+Mark your calendars, tell your friends, and get ready for a day full of inspiring talks, networking opportunities, and the vibrant energy of the Symfony community…
+
+
+
+
+
We’re thrilled to announce that SymfonyDay Montreal is happening on June 4, 2026! 🎉
+
+
Mark your calendars, tell your friends, and get ready for a day full of inspiring talks, networking opportunities, and the vibrant energy of the Symfony community in Canada! 🍁
Submit your talk for SymfonyDay Montreal 2026! Every selected speaker will receive a complimentary conference ticket and a speaker gift! Speakers who do not live in the conference city will also have their travel and accommodation expenses covered.
+
+
If you have never spoken at a conference before, you can take advantage of our speaker mentoring program! Experienced speakers will gladly assist you in preparing for your conference, whether it involves creating your slides or rehearsing your talk. Feel free to seek advice in the #diversity or #speaker-mentoring channels on the Symfony Devs Slack or include comments requesting guidance when submitting your talk proposal.
+
+
+
+
🤝 Want to support the event?
+
+
Become a sponsor and showcase your brand to an engaged audience of developers.
+
+
⚡Contact Hadrien Cren by email to learn more about the options.
+ ]]>
+ https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Thu, 11 Sep 2025 14:30:00 +0200
+ https://symfony.com/blog/save-the-date-symfonyday-montreal-2026?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/symfonycon-amsterdam-2025-call-for-it-student-volunteers-volunteer-learn-and-connect?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+
+
+
+🎓SymfonyCon Amsterdam 2025: Call for IT Student Volunteers!
+
+Are you an IT or computer science student looking to gain hands-on experience, grow your network, and take part in an international tech event? 🎤✨
+Symfony is looking for motivated student…
+
+
+
+
+
🎓SymfonyCon Amsterdam 2025: Call for IT Student Volunteers!
+
+
Are you an IT or computer science student looking to gain hands-on experience, grow your network, and take part in an international tech event? 🎤✨
+Symfony is looking for motivated student volunteers to join the adventure at SymfonyCon Amsterdam 2025!
+
+
📍Where: Amsterdam, The Netherlands
+
+
🗓️When: November 28–29, 2025
+
+
Volunteering with us means helping out for one day and getting a free ticket to attend the conference the other day, so you don’t miss out on the action!
+
+
+
+
💼 Volunteer Tasks
+
+
As a volunteer, you’ll be assigned to:
+
+
✔️Welcome attendees
+
+
✔️Provide directions and information
+
+
✔️Help ensure everything runs smoothly!
+
+
+
+
✅ Requirements
+
+
To apply, you must:
+
+
✔️Be a student in computer science / IT (all backgrounds welcome!)
+
+
✔️Be curious about careers in development or tech
+
+
✔️Be available for at least one full day during the event
+
+
✔️Speak English (conference language)
+
+
+
+
🎁 What You Get
+
+
Free access to the full 2-day SymfonyCon Amsterdam 2025 conference
+
+
✔️Opportunities to network with developers, speakers, and community leaders
+
+
✔️Delicious lunches included
+
+
✔️An unforgettable experience in an inclusive and friendly environment!
+
+
+
+
🚫 Please note:
+
+
We are unable to cover:
+
+
✔️Travel or accommodation costs ✔️Dinners or evening expenses
+
+
+
+
💌 How to Apply:
+
+
Interested? Send us an email at events@symfony.com with:
+
+
✔️Your name, school, and area of study
+
+
✔️The reason you’d like to volunteer
+
+
✔️Confirmation of your availability on November 28 or 29, 2025
+
+
+
+
Be part of the Symfony community, gain valuable experience, and help make this event amazing!
+We can’t wait to meet you! 🧑💻💬 #SymfonyCon
+ ]]>
+ https://symfony.com/blog/symfonycon-amsterdam-2025-call-for-it-student-volunteers-volunteer-learn-and-connect?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Wed, 10 Sep 2025 09:00:00 +0200
+ https://symfony.com/blog/symfonycon-amsterdam-2025-call-for-it-student-volunteers-volunteer-learn-and-connect?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/seven-symfony-core-team-members-speaking-next-week-at-the-api-platform-conference-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+
+
+The API Platform Conference, the only event dedicated to the API Platform framework and its ecosystem, will take place next week in Lille, France, on September 18–19, 2025. More than 30 talks are scheduled, in both French and English, covering case…
+
+
+
+The API Platform Conference, the only event dedicated to the API Platform framework and its ecosystem, will take place next week in Lille, France, on September 18–19, 2025. More than 30 talks are scheduled, in both French and English, covering case studies around API Platform, as well as tools and frameworks related to this project, created 10 years ago and now a leading solution for building modern and efficient APIs.
+
+
Among all the incredible speakers and sessions planned during these two days, seven Symfony Core Team members will take the stage at EuraTechnologies, one of Europe’s first startup incubators:
+
+
+
Kévin Dunglas, API Platform's creator, will open the conference with a talk about enhancing your API Platform APIs with Go thanks to FrankenPHP.
+
Fabien Potencier, Symfony's creator, will speak about how LLMs are going to change the way we build APIs.
+
Nicolas Grekas, Symfony's principal core team member, will deliver a keynote on Symfony’s configuration formats and their impact on DX on Friday afternoon, September 19.
+
Mathieu Santostefano will show how to integrate an LLM into a chatbot with a JS client, a PHP API, and Mercure.
+
Jérôme Tamarelle will help you optimize your use of MongoDB with API Platform.
+
Mathias Arlaud will present how to streamline API performance with JsonStreamer & ESA.
+
Alexandre Daubois will present PIE and explain how this PHP extension is set to transform the way we use PHP extensions.
+
+
+
More information about the speakers and schedule can be found on the event’s website.
+
+
Also, for the first time—and after months of careful stitching—the first FrankenPHP elePHPant plushies will be on sale at a special conference price in Lille. Don’t miss your chance to attend this incredible event, and make sure to secure your seat before ticketing closes this week.
+Any questions about the API Platform Conference 2025? Get in touch with Les-Tilleuls.coop, the event organizers.
+ ]]>
+ https://symfony.com/blog/seven-symfony-core-team-members-speaking-next-week-at-the-api-platform-conference-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Mon, 08 Sep 2025 15:30:00 +0200
+ https://symfony.com/blog/seven-symfony-core-team-members-speaking-next-week-at-the-api-platform-conference-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/a-week-of-symfony-975-september-1-7-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+ This week, Symfony development activity focused on merging new features for the upcoming Symfony 7.4 and 8.0 versions: added a Video constraint, introduced an access_decision() Twig function to get the security voter decision details, and added support for…
+ This week, Symfony development activity focused on merging new features for the upcoming Symfony 7.4 and 8.0 versions: added a Video constraint, introduced an access_decision() Twig function to get the security voter decision details, and added support for DTOs in Input attributes for invokable commands. We also announced that Symfony will provide the official MCP SDK for PHP, announced the first talks of the SymfonyCon Amsterdam 2025 conference and continued celebrating the legacy of Ryan Weaver.
+
+
Symfony development highlights
+
+
This week, 76 pull requests were merged (59 in code and 17 in docs) and 44 issues were closed (35 in code and 9 in docs). Excluding merges, 37 authors made 18,757 additions and 6,253 deletions. See details for code and docs.
+ ]]>
+ https://symfony.com/blog/a-week-of-symfony-975-september-1-7-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Sun, 07 Sep 2025 09:23:00 +0200
+ https://symfony.com/blog/a-week-of-symfony-975-september-1-7-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/symfony-to-provide-the-official-mcp-sdk?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+ Today, we are announcing that Symfony teamed up with The PHP Foundation, and
+Anthropic's MCP team to launch the official MCP SDK. Our goal is a
+framework-agnostic, production-ready library the entire PHP ecosystem can rely on.
+The Symfony team will lead maintenance,…
+ Today, we are announcing that Symfony teamed up with The PHP Foundation, and
+Anthropic's MCP team to launch the official MCP SDK. Our goal is a
+framework-agnostic, production-ready library the entire PHP ecosystem can rely on.
+
The Symfony team will lead maintenance, with contributions from the broader
+community, including Kyrian Obikwelu (PHP-MCP).
Less than a year ago, Anthropic released the Model Context Protocol (MCP), an
+open protocol to standardize how LLM-powered client applications interact with
+context-providing servers. The specification is designed around common
+capabilities that are used when building agents and complex workflows, like
+Prompts, Tools, Resources, and more.
+
The specification is designed around common capabilities, used when building
+agents and complex workflow — such as Prompts, Tools, and Resources — and has
+already seen broad adoption across the software industry. Published under the
+MIT license, MCP first shipped with SDKs in Python and TypeScript, sparking
+hundreds of open-source clients, servers, and SDKs in more languages.
When we kicked off the Symfony AI initiative, a low-level MCP SDK was already
+part of it. It was rather a first draft - not yet feature complete - and meant
+to evolve as part of the initiative. In parallel, the PHP-MCP project
+maintained multiple MCP-related packages as well.
+
Now, thanks to an initiative led by The PHP Foundation in collaboration
+with Anthropic's MCP team, Symfony and PHP-MCP are joining forces to
+provide the official PHP MCP SDK.
The repository and package are now public, but we have not yet tagged a stable
+release. As with the Symfony AI initiative, we're inviting the community to
+help shape the SDK together.
+ ]]>
+ https://symfony.com/blog/symfony-to-provide-the-official-mcp-sdk?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Fri, 05 Sep 2025 11:01:00 +0200
+ https://symfony.com/blog/symfony-to-provide-the-official-mcp-sdk?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/celebrating-ryan-weaver-through-his-talks?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+ Ryan Weaver was a gifted teacher, a brilliant developer, and a true friend to our
+community. In the past days, we've remembered his kindness, his humor, and the joy he brought
+to so many developers around the world. Today, we'd like to honor his legacy in…
+ Ryan Weaver was a gifted teacher, a brilliant developer, and a true friend to our
+community. In the past days, we've remembered his kindness, his humor, and the joy he brought
+to so many developers around the world. Today, we'd like to honor his legacy in
+a special way: by making all of his Symfony conference talks free to watch.
+
While the technical details in these talks may eventually become outdated,
+Ryan's unique ability to teach, his infectious enthusiasm, and his remarkable
+talent for making complex concepts accessible will remain timeless. This is our
+way of ensuring his legacy continues to inspire developers for years to come.
+
+
+
Ryan Weaver delivering a talk during the SymfonyCon 2018 conference
+
+
Many of you will remember his iconic talk at SymfonyCon 2022 in Disneyland
+Paris, where he playfully delivered his talk while dressed as various Disney
+characters (watch it here).
+Or his most recent talk at SymfonyOnline January 2024, celebrating the third
+anniversary of Symfony UX with the same energy that had defined his career
+(watch it here).
+
Ryan's expertise extended far beyond front-end. During SymfonyWorld Online 2020,
+the first conference held entirely online due to the COVID-19 pandemic, he delivered
+a masterclass on the new Symfony security system introduced in version 5.x
+(watch it here).
+
And if you'd like travel back to where it all began, don't miss his very first
+recorded Symfony conference talk at SymfonyCon Warsaw 2013, a front-end session
+that captured the spirit of a young but already brilliant teacher
+(watch it here).
+
These are just a few highlights of Ryan's remarkable contributions on stage,
+now available for everyone to watch. We encourage you to revisit them, to smile,
+to learn, and to feel the passion he poured into every word:
Through his talks, Ryan continues to teach us, not only about Symfony, but about
+joy, creativity, and community. Thank you, Ryan. We'll keep learning from you.
+ ]]>
+ https://symfony.com/blog/celebrating-ryan-weaver-through-his-talks?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Fri, 05 Sep 2025 08:56:00 +0200
+ https://symfony.com/blog/celebrating-ryan-weaver-through-his-talks?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/symfonycon-amsterdam-2025-where-have-the-women-of-tech-history-gone-2-0?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+
+
+
+SymfonyCon Amsterdam 2025, our next annual international Symfony conference, will take place on:
+
+
+November 25 & 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups.
+November 27 & 28 with three…
+
+
+
+
+
SymfonyCon Amsterdam 2025, our next annual international Symfony conference, will take place on:
+
+
+
November 25 & 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups.
+
November 27 & 28 with three English-speaking tracks, an Unconference track, and a lively community evening.
+
+
+
The first part of the schedule is available here, as well as the complete list of workshop topics.
+
+
Stay in the look out for more to come!👀
+
+
+
+
🎤 New speaker announcement!
+
+
We’re thrilled to welcome Laura Durieux, @devgirl__, Web Developer & Twitch Streamer, Freelance, as a speaker at SymfonyCon Amsterdam 2025!
"With the last chapter, we explored the extraordinary contributions of women to the history of computer science. But did you know that was just the tip of the iceberg? After diving deeper into this fascinating subject, I’ve uncovered even more incredible stories to share.
+
+
What about the pioneer behind assembly language? Or the creator of the STP, without which the World Wide Web might never have existed? And what about the brilliant mind behind the ARM architecture?"
+
+
+
+
SymfonyCon Amsterdam 2025 is coming fast!
+
+
🎟️ Register now to enjoy regular price and choose the ticket that suits you the best among workshop, conference and combo ticket. Check all the details and get your ticket!
+
+
🖥️ Check out the exciting 14 workshop topicson this page.
🫵 Participate in the Unconference track, a participant-driven format where attendees shape the content and discussions in real-time. Have a topic you're passionate about? Claim your slot by emailing us at events@symfony.com and set the stage for an unforgettable experience. Each unconference talk lasts 20 minutes with a screen and projector available on both days.
+
+
🎉 Celebrate Symfony’s 20th Anniversary with us at the Kanarie Club! Join us for an unforgettable evening of drinks, music, and good vibes on Thursday, November 27 from 7:30 to 10:30 pm at the vibrant Kanarie Club, right in the heart of De Hallen — a lively food court with 21 amazing stands (grab your favorite bites, bring them to the Kanarie club!) Dress as you like or simply add a touch of fun!✨
+
+
💻 Save the date for the Symfony hackathon on November 29. Everyone is welcome to join the hackday! Whether you're an experienced contributor or new to the community, your participation is highly valued as it brings a fresh perspective! More details are available here. The hackathon will be hosted in Volkshotel Amsterdam from 09:30 a.m. to 3:00 p.m. CET. thanks to the support of Baksla.sh.
+
+
📖 Read our attendee guide for venue, accommodation, and transportation details.
+
+
👕 Complete your Symfony Live profile to inform us of your dietary preferences, T-shirt size and talk preferences!
+
+
+
+
Joins us online!
+
+
💡Follow the "conference" blog posts to not miss anything!
+
+
Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎
+ ]]>
+ https://symfony.com/blog/symfonycon-amsterdam-2025-where-have-the-women-of-tech-history-gone-2-0?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Thu, 04 Sep 2025 17:45:00 +0200
+ https://symfony.com/blog/symfonycon-amsterdam-2025-where-have-the-women-of-tech-history-gone-2-0?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/symfonycon-amsterdam-2025-make-your-ai-useful-with-mcp?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+
+
+
+SymfonyCon Amsterdam 2025, our next annual international Symfony conference, will take place on:
+
+
+November 25 & 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups.
+November 27 & 28 with three…
+
+
+
+
+
SymfonyCon Amsterdam 2025, our next annual international Symfony conference, will take place on:
+
+
+
November 25 & 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups.
+
November 27 & 28 with three English-speaking tracks, an Unconference track, and a lively community evening.
+
+
+
The first part of the schedule is available here, as well as the complete list of workshop topics.
In his talk “Make your AI useful with MCP” he will go over the MCP protocol, why we care and how to use it to create an AI service that is helpful.
+
+
"Wouldn't it be great if we can use our LLM to answer very specific questions? Wouldn't it be great to move away from a general purpose machine to something tailor made for me or my users? If we could stop asking questions like "What is the area of a sphere?" and more like:
+
+
+
"What is the status on my last order?"
+
"What are the total fees given to a supplier X?"
+
"How many images have I uploaded this year?"
+
+
+
+
+
SymfonyCon Amsterdam 2025 is coming fast!
+
+
🎟️ Register now to enjoy regular price and choose the ticket that suits you the best among workshop, conference and combo ticket. Check all the details and get your ticket!
+
+
🖥️ Check out the exciting 14 workshop topicson this page.
🫵 Participate in the Unconference track, a participant-driven format where attendees shape the content and discussions in real-time. Have a topic you're passionate about? Claim your slot by emailing us at events@symfony.com and set the stage for an unforgettable experience. Each unconference talk lasts 20 minutes with a screen and projector available on both days.
+
+
🎉 Celebrate Symfony’s 20th Anniversary with us at the Kanarie Club! Join us for an unforgettable evening of drinks, music, and good vibes on Thursday, November 27 from 7:30 to 10:30 pm at the vibrant Kanarie Club, right in the heart of De Hallen — a lively food court with 21 amazing stands (grab your favorite bites, bring them to the Kanarie club!) Dress as you like or simply add a touch of fun!✨
+
+
💻 Save the date for the Symfony hackathon on November 29. Everyone is welcome to join the hackday! Whether you're an experienced contributor or new to the community, your participation is highly valued as it brings a fresh perspective! More details are available here. The hackathon will be hosted in Volkshotel Amsterdam from 09:30 a.m. to 3:00 p.m. CET. thanks to the support of Baksla.sh.
+
+
📖 Read our attendee guide for venue, accommodation, and transportation details.
+
+
👕 Complete your Symfony Live profile to inform us of your dietary preferences, T-shirt size and talk preferences!
+
+
+
+
Joins us online!
+
+
💡Follow the "conference" blog posts to not miss anything!
+
+
Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎
+ ]]>
+ https://symfony.com/blog/symfonycon-amsterdam-2025-make-your-ai-useful-with-mcp?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Wed, 03 Sep 2025 12:15:00 +0200
+ https://symfony.com/blog/symfonycon-amsterdam-2025-make-your-ai-useful-with-mcp?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/symfonycon-amsterdam-2025-practical-ai-integrations-with-symfony?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+
+
+
+SymfonyCon Amsterdam 2025, our next annual international Symfony conference, will take place on:
+
+
+November 25 & 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups.
+November 27 & 28 with three…
+
+
+
+
+
SymfonyCon Amsterdam 2025, our next annual international Symfony conference, will take place on:
+
+
+
November 25 & 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups.
+
November 27 & 28 with three English-speaking tracks, an Unconference track, and a lively community evening.
+
+
+
The first part of the schedule is available here, as well as the complete list of workshop topics.
+
+
Stay in the look out for more to come!👀
+
+
+
+
🎤 New speaker announcement!
+
+
We’re thrilled to welcome Christopher Hertel(@el_stoffel), Software architect, AMCS Group, as a speaker at SymfonyCon Amsterdam 2025! 🙌
+
+
🤖 In his talk “Practical AI Integrations with Symfony”, Christopher cuts through the AI buzz to show you real, actionable ways to bring AI-powered features, far beyond chatbots, into your PHP applications and scalable architectures.
+
+
If you’re exploring how to integrate Symfony AI, searching for inspiration, or wondering what’s actually possible, this talk is your roadmap to making AI work for you. 🚀
+
+
+
+
SymfonyCon Amsterdam 2025 is coming fast!
+
+
🎟️ Register now to enjoy regular price and choose the ticket that suits you the best among workshop, conference and combo ticket. Check all the details and get your ticket!
+
+
🖥️ Check out the exciting 14 workshop topicson this page.
🫵 Participate in the Unconference track, a participant-driven format where attendees shape the content and discussions in real-time. Have a topic you're passionate about? Claim your slot by emailing us at events@symfony.com and set the stage for an unforgettable experience. Each unconference talk lasts 20 minutes with a screen and projector available on both days.
+
+
🎉 Celebrate Symfony’s 20th Anniversary with us at the Kanarie Club! Join us for an unforgettable evening of drinks, music, and good vibes on Thursday, November 27 from 7:30 to 10:30 pm at the vibrant Kanarie Club, right in the heart of De Hallen — a lively food court with 21 amazing stands (grab your favorite bites, bring them to the Kanarie club!) Dress as you like or simply add a touch of fun!✨
+
+
💻 Save the date for the Symfony hackathon on November 29. Everyone is welcome to join the hackday! Whether you're an experienced contributor or new to the community, your participation is highly valued as it brings a fresh perspective! More details are available here. The hackathon will be hosted in Volkshotel Amsterdam from 09:30 a.m. to 3:00 p.m. CET. thanks to the support of Baksla.sh.
+
+
📖 Read our attendee guide for venue, accommodation, and transportation details.
+
+
👕 Complete your Symfony Live profile to inform us of your dietary preferences, T-shirt size and talk preferences!
+
+
+
+
Joins us online!
+
+
💡Follow the "conference" blog posts to not miss anything!
+
+
Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎
+ ]]>
+ https://symfony.com/blog/symfonycon-amsterdam-2025-practical-ai-integrations-with-symfony?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+
+ Tue, 02 Sep 2025 15:00:00 +0200
+ https://symfony.com/blog/symfonycon-amsterdam-2025-practical-ai-integrations-with-symfony?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list
+
+
+
+ https://symfony.com/blog/a-week-of-symfony-974-august-25-31-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed
+ This week, we celebrated the life and legacy of Ryan Weaver, a beloved teacher, contributor, and friend whose work at SymfonyCasts helped thousands discover the joy of Symfony. His kindness, humor, and dedication to lifting others up made our community stronger,…
+ This week, we celebrated the life and legacy of Ryan Weaver, a beloved teacher, contributor, and friend whose work at SymfonyCasts helped thousands discover the joy of Symfony. His kindness, humor, and dedication to lifting others up made our community stronger, and his spirit will continue to inspire everything we build. To honor Ryan, we encourage the community to keep learning, keep sharing, and support SymfonyCasts and his family.
+
+
Symfony development highlights
+
+
This week, 28 pull requests were merged (26 in code and 2 in docs) and 19 issues were closed (15 in code and 4 in docs). Excluding merges, 18 authors made 10,219 additions and 5,782 deletions. See details for code and docs.