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 - - <![CDATA[Save the date, SymfonyDay Montreal 2026!]]> - 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… - - Blog 1200X440Px -

- -

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! 🍁

- -

🎟️ Register now to secure your seat, here!

- -
- -

📝Call for papers is open!

- -

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.

- -

We can't wait to see you there!

- -

- Banner Blog -

- -
-
- Sponsor the Symfony project. -
- ]]>
- 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 -
- - <![CDATA[SymfonyCon Amsterdam 2025: Call for IT student volunteers: Volunteer, Learn & Connect!]]> - 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… - - Nl Blog Banner -

- -

🎓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

- -
- -

- Banner Blog -

- -
-
- Sponsor the Symfony project. -
- ]]>
- 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 + + <![CDATA[Save the date, SymfonyDay Montreal 2026!]]> + 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… + + Blog 1200X440Px +

+ +

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! 🍁

+ +

🎟️ Register now to secure your seat, here!

+ +
+ +

📝Call for papers is open!

+ +

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.

+ +

We can’t wait to see you there!

+ +

+ Banner Blog +

+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[SymfonyCon Amsterdam 2025: Call for IT student volunteers: Volunteer, Learn & Connect!]]> + 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… + + Nl Blog Banner +

+ +

🎓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

+ +
+ +

+ Banner Blog +

+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[Seven Symfony Core Team Members Speaking Next Week at the API Platform Conference 2025]]> + 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… + + Logo Conference 2025 + +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.

+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[A Week of Symfony #975 (September 1–7, 2025)]]> + 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.

+ +

6.4 changelog:

+ +
    +
  • d33a886: [Inflector, String] fix edge cases
  • +
  • bbfcf84: [Serializer] fix dealing with asymmetric visilibity for properties
  • +
  • e6dfb3d: [HttpFoundation] change session column type from TEXT to STRING in SQL Server
  • +
  • 1dd5bd1: [Messenger] fix incompatibility with expected lowercase columns in Firebird database
  • +
  • 2205b62: [Security] pass attributes to nested ChainUserProviders
  • +
  • 3cd859f: [Cache] make TagAwareAdapter registrable as a service
  • +
  • e3b664d: [Cache] fix internal representation of non-static values
  • +
  • 1ff83e0: [SecurityBundle] prevent accessing the tracked token storage when collecting data
  • +
  • a57c946: [Serializer] fix normalizing objects with accessors having the same name as a property
  • +
  • 713f099: [PHPUnit Bridge] replace backtick operator, deprecated in PHP 8.5, with shell_exec()
  • +
  • c5ac440: [Security] fix HttpUtils::createRequest() when the base request is forwarded
  • +
  • a143fa2: use the empty string instead of null as an array offset
  • +
+ +

7.3 changelog:

+ +
    +
  • 6e42b36: [JsonStreamer] fix decoding iterable lists
  • +
  • 5ff7e23: [DependencyInjection] respect original service class when a proxy is defined
  • +
  • acb2027: [JsonStreamer] fix encoding iterable lists
  • +
  • 87ba5d8: [Config] fix ReflectionClassResource hash validation
  • +
  • 1778811: [DependencyInjection] fix optimizing ClassExistenceResource
  • +
  • e7c41fe: [Validator] fall back to legacy options handling if configured named arguments do not match
  • +
  • 9157094: [Routing] don't rebuild cache when controller action body changes
  • +
  • e265f33: [Messenger] map legacy options to the "sentinel" key when parsing DSNs
  • +
+ +

7.4 changelog:

+ +
    +
  • 67016b9: [Serializer, Validator] add JSON schema for validating and autocompleting YAML config files
  • +
  • 7278aa0: [DependencyInjection] parse attributes found on abstract classes for resource definitions
  • +
  • 145abac: [Runtime] expose the runtime class in $_SERVER['APP_RUNTIME']
  • +
  • 18d7668: [DependencyInjection] allow multiple #[AsDecorator] attributes
  • +
  • 95f0098: [Validator] add Video constraint for validating video files
  • +
  • bae2c06: [DependencyInjection] optimize AutowireRequiredMethodsPass
  • +
  • 4919062: [Security, TwigBridge] add access_decision() and access_decision_for_user()
  • +
  • d052b75: [DoctrineBridge] use a single table in isSameDatabaseChecker
  • +
  • c539c77: [Validator] allow using attributes to declare compile-time constraint metadata
  • +
  • bde5221: [Serializer] allow using attributes to declare compile-time serialization metadata
  • +
  • c102159: [FrameworkBundle] do not parse attributes on abstract classes with DI < 7.4
  • +
  • a9dcea4: [VarExporter] add support for exporting named closures
  • +
  • 75935f8: [DependencyInjection] allow Class::function(...) and global_function(...) closures in PHP DSL for factories
  • +
  • eedf441: [Security, SecurityBundle] dump role hierarchy as mermaid chart
  • +
  • ae256f9: [Console] add #[Input] attribute to support DTOs in invokable commands
  • +
+ +

8.0 changelog:

+ +
    +
  • 1d6ac79: [Security] make UserChainProvider implement AttributesBasedUserProvider
  • +
  • f7e16f1: remove ClassMetadataFactoryCompiler and other legacy cleanups
  • +
+ +

Newest issues and pull requests

+ + + +

Symfony Jobs

+ +

These are some of the most recent Symfony job offers:

+ +
    +
  • Lead Symfony Developer at Buddy & Selly
    +Full-time - €70,000 – €80,000 / year
    +Full remote
    +View details
  • +
  • Lead Symfony Developer at A-Cube API
    +Full-time - €54,000 – €72,000 / year
    +Full remote
    +View details
  • +
  • Backend Symfony Developer at Spyrit
    +Full-time - €40,000 – €55,000 / year
    +Remote + part-time onsite (Versailles, France)
    +View details
  • +
  • Backend Symfony Developer at Evergrowth
    +Full-time - €60,000 – €72,000 / year
    +Full remote
    +View details
  • +
  • Backend Symfony Developer at Eneba
    +Full-time - €55,000 – €66,000 / year
    +Full remote
    +View details
  • +
+ +

You can publish a Symfony job offer for free on symfony.com.

+ +

They talked about us

+ + + +

Call to Action

+ + + +
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[Symfony to Provide the Official MCP SDK]]> + 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).

+
+

Model Context Protocol

+

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.

+
+
+

Symfony joining forces for the official MCP SDK

+

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.

+
+
+ +
$ composer require mcp/sdk
+
+
+
+
+

Still work in progress

+

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.

+
+

How to Get Started

+ +
+
+
+

Closing Notes

+

We would like to thank David Soria Parra (Anthropic) and Roman Pronskiy (PHP Foundation) for their initiative, and Kyrian Obikwelu, of PHP-MCP, for heading into this endeavor with us.

+

As part of this transition, the current symfony/ai MCP SDK package will be deprecated and removed.

+
+
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[Celebrating Ryan Weaver Through His Talks]]> + 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 at SymfonyCon 2018 +

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:

+

Watch all of Ryan Weaver's talks at Symfony Conferences

+

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.

+
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[SymfonyCon Amsterdam 2025: Where have the women of tech history gone? 2.0]]> + 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 &amp; 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups. +November 27 &amp; 28 with three… + + Laura Durieux Blog +

+ +

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!

+ +

In her talk SymfonyCon Amsterdam 2025: Where have the women of tech history gone? 2.0 you will travel into the history of computer science to uncover the remarkable achievements of even more amazing women.

+ +

"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 topics on this page.

+ +

🎤 Explore the great lineup of conference talks here, featuring top-notch expert speakers as Fabien Potencier, Christopher Hertel, Robin Chalas, Tobias Nyholm, Andreas Braun, Anna Filina, Pauline Vos, Stefan Koopmanschap, Nicolas Grekas, Iain Cambridge, Sebastian Bergmann, Konrad Oboza, Alexandre Salome, Antoine Bluchet, Kévin Dunglas, Rob Allen, Mathias Arlaud, Benjamin Eberlei, Viktor Pikaev, Viktor Pikaev, Romain Neutron.

+ +

🫵 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 🌎

+ +

+ Banner Blog +

+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[SymfonyCon Amsterdam 2025: Make your AI useful with MCP]]> + 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 &amp; 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups. +November 27 &amp; 28 with three… + + Tobias Nyholm +

+ +

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 Tobias Nyholm (@TobiasNyholm), Eneba, as a speaker at SymfonyCon Amsterdam 2025! 🙌

+ +

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 topics on this page.

+ +

🎤 Explore the great lineup of conference talks here, featuring top-notch expert speakers as Fabien Potencier, Christopher Hertel, Robin Chalas, Tobias Nyholm, Andreas Braun, Anna Filina, Pauline Vos, Stefan Koopmanschap, Nicolas Grekas, Iain Cambridge, Sebastian Bergmann, Konrad Oboza, Alexandre Salome, Antoine Bluchet, Kévin Dunglas, Rob Allen, Mathias Arlaud, Benjamin Eberlei, Viktor Pikaev, Viktor Pikaev, Romain Neutron.

+ +

🫵 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 🌎

+ +

+ Banner Blog +

+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[SymfonyCon Amsterdam 2025: Practical AI Integrations with Symfony]]> + 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 &amp; 26 with two days of hands-on workshops to learn, practice, and enhance your skills in small groups. +November 27 &amp; 28 with three… + + At El Stoffel +

+ +

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 topics on this page.

+ +

🎤 Explore the great lineup of conference talks here, featuring top-notch expert speakers as Fabien Potencier, Christopher Hertel, Robin Chalas, Tobias Nyholm, Andreas Braun, Anna Filina, Pauline Vos, Stefan Koopmanschap, Nicolas Grekas, Iain Cambridge, Sebastian Bergmann, Konrad Oboza, Alexandre Salome, Antoine Bluchet, Kévin Dunglas, Rob Allen, Mathias Arlaud, Benjamin Eberlei, Viktor Pikaev, Viktor Pikaev, Romain Neutron.

+ +

🫵 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 🌎

+ +

+ Banner Blog +

+ +
+
+ Sponsor the Symfony project. +
+ ]]>
+ 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 +
+ + <![CDATA[A Week of Symfony #974 (August 25–31, 2025)]]> + 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.

+ +

6.4 changelog:

+ +
    +
  • 179b553: [FrameworkBundle] don't collect CLI profiles if the profiler is disabled
  • +
  • 1fc42d8: [HttpClient] fix PHP 8.5 deprecation using str_increment()
  • +
  • 89c0360: [Yaml] fix scope resolution operator in flow mapping keys
  • +
  • 8a54b6e: [Intl] add metadata about currencies' validity dates
  • +
  • ece4f3e: [Security] fix attribute-based chained user providers
  • +
+ +

7.3 changelog:

+ +
    +
  • 8ec0d38: [Serializer] don't fallback to default serializer if tags specify a named one
  • +
  • 62eb019: [TypeInfo] prevent interfaces extending BackedEnum to be treated as BackedEnums
  • +
+ +

7.4 changelog:

+ +
    +
  • 3ab58a4: [Routing] make RoutingControllerPass and AttributeServicesLoader final
  • +
  • b0f0bdf: [DependencyInjection] add routing.controller to the list of known DI tags
  • +
  • c228caa: [Runtime] support runtime options as a string
  • +
  • 07273df: [ObjectMapper] cache attributes in memory
  • +
+ +

8.0 changelog:

+ +
    +
  • cbcb165: [DependencyInjection] throw when a service's id a non-existing FQCN
  • +
  • bff8811: [Validator] require a top-level domain by default in the Url constraint
  • +
+ +

Newest issues and pull requests

+ + + +

Symfony Jobs

+ +

These are some of the most recent Symfony job offers:

+ +
    +
  • Lead Symfony Developer at A-Cube API
    +Full-time - €54,000 – €72,000 / year
    +Full remote
    +View details
  • +
  • Backend Symfony Developer at Spyrit
    +Full-time - €40,000 – €55,000 / year
    +Remote + part-time onsite (Versailles, France)
    +View details
  • +
  • Backend Symfony Developer at Evergrowth
    +Full-time - €60,000 – €72,000 / year
    +Full remote
    +View details
  • +
  • Backend Symfony Developer at Eneba
    +Full-time - €55,000 – €66,000 / year
    +Full remote
    +View details
  • +
  • Backend Symfony Developer at Citrus Systems
    +Full-time - €3,000 – €6,000 / month
    +Remote + part-time onsite (Belgrade, Serbia)
    +View details
  • +
+ +

You can publish a Symfony job offer for free on symfony.com.

+ +

They talked about us

+ + + +

Call to Action

+ + + +
+
+ Sponsor the Symfony project. +
+ ]]>
+ https://symfony.com/blog/a-week-of-symfony-974-august-25-31-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed + + Sun, 31 Aug 2025 09:16:00 +0200 + https://symfony.com/blog/a-week-of-symfony-974-august-25-31-2025?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list +
+
+