<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SensioLabs</title>
    <description>The latest articles on DEV Community by SensioLabs (@sensiolabs).</description>
    <link>https://dev.to/sensiolabs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1221%2F050e9b52-7339-49a7-8c4d-552161aaab1d.jpg</url>
      <title>DEV Community: SensioLabs</title>
      <link>https://dev.to/sensiolabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sensiolabs"/>
    <language>en</language>
    <item>
      <title>API Platform Con 2025 - Day 2</title>
      <dc:creator>Thérage Kévin</dc:creator>
      <pubDate>Wed, 24 Sep 2025 08:00:19 +0000</pubDate>
      <link>https://dev.to/sensiolabs/api-platform-con-2025-day-2-ebd</link>
      <guid>https://dev.to/sensiolabs/api-platform-con-2025-day-2-ebd</guid>
      <description>&lt;p&gt;I had the opportunity to attend the API Platform Con 2025 thanks to SensioLabs and here is what I learned through the talks I viewed.&lt;/p&gt;




&lt;h2&gt;
  
  
  How LLMs are changing the way we should build APIs (Fabien Potentier)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Slides of this talk are available : &lt;a href="https://speakerdeck.com/fabpot/how-ai-agents-are-changing-the-way-we-should-build-apis" rel="noopener noreferrer"&gt;https://speakerdeck.com/fabpot/how-ai-agents-are-changing-the-way-we-should-build-apis&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fabien Potentier shared insights about how Large Language Models are fundamentally changing the way we need to think about API design. As he mentioned, this is a world that changes so fast that some assertions might already be outdated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agents ?
&lt;/h3&gt;

&lt;p&gt;LLMs are evolving beyond simple text generation into autonomous agents. According to Anthropic's definition, an agent is an LLM using tools in a loop. These LLMs are self-directed - they can reason about things, they can plan, and have memory.&lt;/p&gt;

&lt;p&gt;An AI agent is kind of a mix between a machine and a human, combining the computational power of machines with human-like reasoning capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who can consume your app ?
&lt;/h3&gt;

&lt;p&gt;Back in the days, the consumers were clearly defined:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human users only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CLI tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;API:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only for machines&lt;/li&gt;
&lt;li&gt;Semi-private (to decouple frontend) or public&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nowadays, APIs are mostly used to expose data, but AI agents have changed the game completely. They are able to interact with all three interfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Websites can be scraped by AI&lt;/strong&gt; - agents can navigate and extract information from web interfaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI tools can be used through MCP servers&lt;/strong&gt; - providing structured tool access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APIs&lt;/strong&gt; - LLMs (e.g., in chatbots) are often wrappers on top of APIs. Furthermore, LLMs can also write API calls directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But all three have different expectations, and this creates new challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge: APIs for Humans vs. Machines vs. AI Agents
&lt;/h3&gt;

&lt;p&gt;APIs are optimized for machines, but when something breaks, you need a human in the loop. However, AI agents are autonomous but, like humans, they need help and guidance.&lt;/p&gt;

&lt;p&gt;Take HTTP status codes as an example. They provide information about problems, but AI agents need more context.&lt;br&gt;
HTTP responses can provide context about errors, but responses provided by APIs might not be up-to-date or accurate, causing LLMs to get stuck.&lt;/p&gt;

&lt;p&gt;Here is a common workflow pattern followed by LLM : Thought → Action → Observation.&lt;br&gt;
Without guidance provided via prompts, it can loop over the same problem, encountering the same Observation after performing the same Action—potentially forever.&lt;br&gt;
LLMs will try to guess and self-correct, which is probably bad for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Costly&lt;/strong&gt; - more API calls and processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time loss&lt;/strong&gt; - inefficient problem resolution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource greedy&lt;/strong&gt; - GPU time and electricity are consumed without solving the problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; The fewer round trips you have with an LLM, the more "deterministic" it becomes, even though LLMs are inherently not deterministic.&lt;/p&gt;
&lt;h3&gt;
  
  
  Best Practices for LLM-Friendly APIs
&lt;/h3&gt;

&lt;p&gt;Everything that is valid for LLMs is also valid for humans.&lt;/p&gt;
&lt;h4&gt;
  
  
  Error Messages
&lt;/h4&gt;

&lt;p&gt;Be precise with your error messages: "Bad date format. Use 'YYYY-MM-DD'."&lt;br&gt;
Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fewer tokens consumed&lt;/li&gt;
&lt;li&gt;Smaller context window usage&lt;/li&gt;
&lt;li&gt;Faster resolution&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Consistent Naming
&lt;/h4&gt;

&lt;p&gt;Use the same naming pattern everywhere. For example, use &lt;code&gt;user_id&lt;/code&gt; consistently across all endpoints.&lt;br&gt;
Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable patterns&lt;/li&gt;
&lt;li&gt;LLMs like consistency&lt;/li&gt;
&lt;li&gt;Easier to understand and use&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Documentation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Fix examples and remove outdated content&lt;/li&gt;
&lt;li&gt;Fewer problems and hallucinations&lt;/li&gt;
&lt;li&gt;Consider using &lt;code&gt;llms.txt&lt;/code&gt; files - documentation specifically formatted for LLMs in Markdown&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Performance Considerations
&lt;/h4&gt;

&lt;p&gt;AI agents are slow, so reducing the number of requests provides a significant performance boost.&lt;/p&gt;
&lt;h4&gt;
  
  
  Intent-First API Design
&lt;/h4&gt;

&lt;p&gt;Design your APIs to capture and preserve user intent rather than just exposing CRUD operations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing Challenges
&lt;/h3&gt;

&lt;p&gt;Testing AI agents is super difficult because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLMs are not deterministic&lt;/li&gt;
&lt;li&gt;You need to set temperature to 0 for more consistent results&lt;/li&gt;
&lt;li&gt;Use concise prompts&lt;/li&gt;
&lt;li&gt;Ultimately, you need a human to judge the quality of actions performed by the LLM, making automated testing complex&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Technical Considerations
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Tokens vs. Text
&lt;/h4&gt;

&lt;p&gt;Understanding tokenization is crucial. Tools like &lt;a href="https://tiktokenizer.vercel.app" rel="noopener noreferrer"&gt;tiktokenizer.vercel.app&lt;/a&gt; help visualize how text is tokenized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language matters:&lt;/strong&gt; English costs less in tokens than French or Japanese for example&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique IDs are problematic:&lt;/strong&gt; UUIDs are bad for tokenizers, ULIDs are better&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shorter is not always better&lt;/strong&gt; in terms of token efficiency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date formats matter&lt;/strong&gt; for token consumption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON is not the best format&lt;/strong&gt; for LLMs - Markdown is better and uses fewer tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More tokens require more money and create larger context windows, which negatively impact AI agent response times and relevance.&lt;/p&gt;
&lt;h4&gt;
  
  
  Security and Credentials
&lt;/h4&gt;

&lt;p&gt;AI agents are bad at dealing with credentials. The solution is to use MCP (Model Context Protocol) servers that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle credentials securely&lt;/li&gt;
&lt;li&gt;Provide tools to AI agents&lt;/li&gt;
&lt;li&gt;Give limited scope permissions to MCP actions&lt;/li&gt;
&lt;li&gt;Act as a secure intermediary between the LLM and your APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Log Everything
&lt;/h3&gt;

&lt;p&gt;Given the complexity and unpredictability of AI agent interactions, comprehensive logging becomes essential for debugging and improving the system.&lt;/p&gt;
&lt;h3&gt;
  
  
  The New Experience: AX (AI Experience)
&lt;/h3&gt;

&lt;p&gt;Fabien introduced the concept of AX (AI Experience) alongside the familiar UX (User Experience) and DX (Developer Experience). This represents a new dimension of API design focused on how well your API works with AI agents.&lt;/p&gt;

&lt;p&gt;Key aspects of good AX include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up-to-date documentation and examples (avoiding outdated examples that could mislead the LLM)&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;llms.txt&lt;/code&gt; files with all useful documentation for the LLM in Markdown format&lt;/li&gt;
&lt;li&gt;Clear, consistent error messages&lt;/li&gt;
&lt;li&gt;Intent-preserving API design&lt;/li&gt;
&lt;li&gt;Efficient token usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fascinating aspect is that many improvements for AX also benefit traditional DX, making APIs better for both human developers and AI agents.&lt;/p&gt;


&lt;h2&gt;
  
  
  Build a decoupled application with API Platform and Vue.js (Nathan de Pachtere)
&lt;/h2&gt;

&lt;p&gt;Nathan de Pachtere shared his experience building decoupled applications using API Platform for the backend and Vue.js for the frontend. His insights covered the differences between headless and decoupled approaches, practical implementation strategies, and the benefits of monorepo architecture.&lt;/p&gt;
&lt;h3&gt;
  
  
  Headless
&lt;/h3&gt;

&lt;p&gt;Headless architecture involves creating a business-focused API that anyone can use independently. Think of the GitHub API - it's designed as a standalone service that provides all the functionality needed to interact with GitHub's features, completely independent of any specific frontend implementation.&lt;/p&gt;

&lt;p&gt;The goal is to create business logic and provide an API that everyone can utilize for their own purposes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Decoupled
&lt;/h3&gt;

&lt;p&gt;Decoupled architecture is similar but more focused. You provide a frontend that relies specifically on your API, creating what's essentially a backend-for-frontend pattern. The API doesn't seem to be made for independent use outside of the specific application - it's tailored to serve the frontend's exact needs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Choose This Approach?
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Advantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separation of responsibilities&lt;/strong&gt; - Clear boundaries between frontend and backend concerns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team management&lt;/strong&gt; - Enables specialist teams to work independently on their expertise areas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capitalization&lt;/strong&gt; - Reusable components and logic across different projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future-proofing&lt;/strong&gt; - AI might be the interface used in the future, making an API-first approach valuable&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Disadvantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt; - More complex setup for existing projects that need to be refactored&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Headless Implementation
&lt;/h3&gt;
&lt;h4&gt;
  
  
  using API Platform
&lt;/h4&gt;

&lt;p&gt;The process follows a business-driven approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Represent the API based on business needs&lt;/strong&gt; - Focus on what the business actually does&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translate into entities and workflows&lt;/strong&gt; - Convert business processes into technical implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write only the necessary code&lt;/strong&gt; - Keep it simple initially&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then optimize and refactor&lt;/strong&gt; - Improve performance and code quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate&lt;/strong&gt; - Continuously improve based on feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go beyond CRUD&lt;/strong&gt; - Implement meaningful business operations, not just basic data manipulation&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Providing API Keys
&lt;/h4&gt;

&lt;p&gt;For machine-to-machine authentication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create a simple interface&lt;/strong&gt; for creating/deleting configurable keys with specific permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider external identity providers&lt;/strong&gt; like Keycloak or Zitadel for more advanced use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Important principle:&lt;/strong&gt; Don't mix human users with machine users - they have different needs and security requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nathan emphasized making tests simple and easy to implement, integrating them naturally into the development workflow rather than treating them as an afterthought.&lt;/p&gt;
&lt;h4&gt;
  
  
  Deprecation Strategy
&lt;/h4&gt;

&lt;p&gt;When evolving your API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deprecate endpoints, resources, and properties&lt;/strong&gt; gradually&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give consumers time to adapt&lt;/strong&gt; to changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communicate changes clearly&lt;/strong&gt; before removing functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach maintains backward compatibility while allowing the API to evolve.&lt;/p&gt;
&lt;h3&gt;
  
  
  Decoupled Implementation
&lt;/h3&gt;
&lt;h4&gt;
  
  
  using Vue.js
&lt;/h4&gt;

&lt;p&gt;Nathan chose Vue.js for several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Independent and community-driven&lt;/strong&gt; - Not controlled by a single corporation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composition API (Vue 3)&lt;/strong&gt; - Promotes code reusability and better organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excellent Developer Experience&lt;/strong&gt; - Great tooling and development workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top performance&lt;/strong&gt; - Fast and efficient (until the next framework comes along, as he joked)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  API Connection
&lt;/h4&gt;

&lt;p&gt;For connecting the Vue.js frontend to the API Platform backend:&lt;/p&gt;
&lt;h5&gt;
  
  
  Code Generation
&lt;/h5&gt;

&lt;p&gt;Use &lt;strong&gt;openapi-ts.dev&lt;/strong&gt; to generate TypeScript types and composables from your OpenAPI specification. This ensures type safety and reduces manual work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important principle:&lt;/strong&gt; Don't use the generated types directly as base objects in your frontend. Create your own models to maintain proper decoupling between frontend and backend representations.&lt;/p&gt;
&lt;h5&gt;
  
  
  HTTP Client and State Management
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tanstack Query&lt;/strong&gt; - For efficient data fetching and caching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript throughout&lt;/strong&gt; - Ensures type safety across the application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code for Vue.js development&lt;/strong&gt; - Better integration compared to JetBrains IDEs for Vue.js work&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;
  
  
  High-Level SDKs
&lt;/h5&gt;

&lt;p&gt;Provide high-level SDKs to facilitate API integration, making it easier for developers to work with your API.&lt;/p&gt;
&lt;h3&gt;
  
  
  Version Management
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Polyrepo vs Monorepo
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Monorepo doesn't mean monolith&lt;/strong&gt; - this is a crucial distinction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo&lt;/strong&gt; = Multiple separate projects in a single repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monolith&lt;/strong&gt; = Single application handling everything&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Monorepo Benefits
&lt;/h4&gt;

&lt;p&gt;The goal is to simplify the workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified way of thinking about code&lt;/strong&gt; - Consistent patterns across projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt; - Shared tooling and configurations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilitates sharing&lt;/strong&gt; - Easy code and component reuse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More efficient teamwork&lt;/strong&gt; - Simplified collaboration and dependency management&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Tooling
&lt;/h4&gt;

&lt;p&gt;Nathan recommended &lt;strong&gt;moonrepo.dev&lt;/strong&gt; as an open-source tool for managing monorepos. You can find more information at &lt;strong&gt;monorepo.tools&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Real-World Example
&lt;/h4&gt;

&lt;p&gt;Nathan shared their experience with enormous benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code generalization&lt;/strong&gt; - Reusable patterns and components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functionality sharing&lt;/strong&gt; - Common libraries across projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technology-based organization&lt;/strong&gt; - Projects use shared libraries organized by technology stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see a practical example of this approach in the &lt;a href="https://github.com/alpsify/lychen" rel="noopener noreferrer"&gt;Lychen project&lt;/a&gt; (&lt;a href="https://lychen.fr/" rel="noopener noreferrer"&gt;lychen.fr&lt;/a&gt;), which demonstrates a well-structured monorepo with clear separation between backend, frontend, and shared tools.&lt;/p&gt;

&lt;p&gt;The Lychen project shows how to organize a monorepo with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt; (API Platform/PHP)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt; (Vue.js/TypeScript)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared tooling&lt;/strong&gt; (Moonrepo, Docker, testing tools)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear technology boundaries&lt;/strong&gt; while maintaining efficient code sharing&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Jean-Beru presents: Fun with flags (Hubert Lenoir)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Slides of this talk are available : &lt;a href="https://jean-beru.github.io/2025_09_apiplatformcon_fun_with_flags" rel="noopener noreferrer"&gt;https://jean-beru.github.io/2025_09_apiplatformcon_fun_with_flags&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Jean-Beru (Hubert Lenoir) presented the fascinating world of feature flags and their practical implementation. As Uncle Ben said in Spider-Man: "With great power comes great responsibility" - and feature flags are indeed a powerful tool that requires careful consideration.&lt;/p&gt;
&lt;h3&gt;
  
  
  What are Feature Flags?
&lt;/h3&gt;

&lt;p&gt;Feature flags (also known as feature flipping or feature toggles) are a software development technique that allows you to turn features on or off without deploying new code. They act as conditional statements in your code that determine whether a particular feature should be enabled or disabled for specific users, environments, or conditions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Types of Feature Flags
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Release Flags
&lt;/h4&gt;

&lt;p&gt;Mainly used to test new features in production environments safely.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Continuous development&lt;/strong&gt; - Even if a feature is not ready, you can continue developing and deploying (not ready = disabled)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe deployment&lt;/strong&gt; - Deploy code with features turned off, then enable them when ready&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradual rollout&lt;/strong&gt; - Enable features for small groups before full release&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Experiment Flags
&lt;/h4&gt;

&lt;p&gt;Used to compare different versions of your application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing&lt;/strong&gt; - Compare different implementations or user experiences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Must be followed by metrics&lt;/strong&gt; - Track performance and user behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial enablement&lt;/strong&gt; - Enable for specific percentages (e.g., 20% of users)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach allows data-driven decisions about which features or implementations work best for your users.&lt;/p&gt;
&lt;h4&gt;
  
  
  Permission Flags
&lt;/h4&gt;

&lt;p&gt;Control access to features based on user permissions or subscription levels.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blocking access based on permissions&lt;/strong&gt; - For example, paid features only available to premium subscribers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-based feature access&lt;/strong&gt; - Different features for different user types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription tiers&lt;/strong&gt; - Enable advanced features for higher-tier customers&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Operational Flags
&lt;/h4&gt;

&lt;p&gt;Security belt and kill switch functionality.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Allow disabling cumbersome features&lt;/strong&gt; - Quickly turn off resource-intensive features during high load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emergency response&lt;/strong&gt; - Disable problematic features without deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance management&lt;/strong&gt; - Control system load by toggling expensive operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more detailed information about feature flag patterns, Martin Fowler has an excellent article at &lt;a href="https://martinfowler.com/articles/feature-toggles.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/feature-toggles.html&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;There are many feature flag providers available in the market, but the implementation doesn't necessarily need to use Symfony's Security component.&lt;/p&gt;
&lt;h4&gt;
  
  
  Why Not Security Component?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restricted to current user context&lt;/strong&gt; - Limitations when flags need to work across different user contexts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication timing issues&lt;/strong&gt; - Authentication happens after routing, which can lead to unwanted forbidden error codes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility needs&lt;/strong&gt; - Custom implementations can better integrate with existing providers like Unleash&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Requirements for a Good Implementation
&lt;/h4&gt;

&lt;p&gt;A solid feature flag system should provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt; - Easy to implement and use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrated debugging&lt;/strong&gt; - Clear visibility into which flags are active&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple sources&lt;/strong&gt; - Ability to switch between different flag providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Various provider support&lt;/strong&gt; - Work with different feature flag services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cacheable&lt;/strong&gt; - Performance optimization through caching mechanisms&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Symfony Integration
&lt;/h4&gt;

&lt;p&gt;There's a work-in-progress FeatureFlag component for Symfony (PR #53213). This component aims to provide native support for feature flags within the Symfony ecosystem.&lt;/p&gt;
&lt;h3&gt;
  
  
  With API Platform
&lt;/h3&gt;

&lt;p&gt;Feature flags can be easily tested via a separated bundle: &lt;a href="https://github.com/ajgarlag/feature-flag-bundle" rel="noopener noreferrer"&gt;ajgarlag/feature-flag-bundle&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Implementation Steps
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Decoration of API Platform provider&lt;/strong&gt; - Use the decorator pattern to wrap existing providers with feature flag logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use FeatureFlag WIP component interface&lt;/strong&gt; - Integrate with the upcoming Symfony FeatureFlag component&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Example with GitLab Provider
&lt;/h4&gt;

&lt;p&gt;GitLab provides a feature flag service that uses Unleash in the background. This integration allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manage flags through GitLab UI&lt;/strong&gt; - Familiar interface for teams already using GitLab&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage Unleash capabilities&lt;/strong&gt; - Powerful feature flag engine under the hood&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrate with CI/CD pipelines&lt;/strong&gt; - Automatic flag management as part of deployment process&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Profiler Integration
&lt;/h4&gt;

&lt;p&gt;The implementation includes Symfony Profiler integration, providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debug information&lt;/strong&gt; - See which flags are active during development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance insights&lt;/strong&gt; - Monitor the impact of feature flag checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Development workflow&lt;/strong&gt; - Easy testing and debugging of flag behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;p&gt;Implementing feature flags brings several significant benefits:&lt;/p&gt;
&lt;h4&gt;
  
  
  Deploy Continuously
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decouple deployment from release&lt;/strong&gt; - Deploy code safely with features disabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce deployment risk&lt;/strong&gt; - Lower chance of breaking production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster iteration cycles&lt;/strong&gt; - More frequent, smaller deployments&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Progressive Testing
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing capabilities&lt;/strong&gt; - Compare different approaches with real users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradual rollouts&lt;/strong&gt; - Start with small user groups and expand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data-driven decisions&lt;/strong&gt; - Make choices based on actual usage metrics&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Quick Turn Off
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No redeployment needed&lt;/strong&gt; - Instantly disable problematic features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emergency response&lt;/strong&gt; - Rapid reaction to production issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business continuity&lt;/strong&gt; - Keep core functionality working while fixing problems&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Separate Code from Feature Release
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Independent timelines&lt;/strong&gt; - Development and business release schedules can differ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketing coordination&lt;/strong&gt; - Align feature releases with marketing campaigns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stakeholder management&lt;/strong&gt; - Give business teams control over when features go live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feature flags represent a powerful paradigm shift in how we think about software deployment and release management, enabling more flexible, safer, and data-driven development practices.&lt;/p&gt;


&lt;h2&gt;
  
  
  PIE : The next Big Thing (Alexandre Daubois)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Extensions ?
&lt;/h3&gt;

&lt;p&gt;Extensions are like composer packages, but written in C, C++, Rust, and now Go.&lt;br&gt;&lt;br&gt;
They live at a lower level, which makes them much faster than pure PHP code.&lt;/p&gt;

&lt;p&gt;Frameworks like &lt;strong&gt;Phalcon&lt;/strong&gt; are themselves shipped as extensions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing a third-party lib
&lt;/h3&gt;

&lt;p&gt;Traditionally, installing an extension is much more painful than a &lt;code&gt;composer install&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
It usually involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Downloading the source code.&lt;/li&gt;
&lt;li&gt;Compiling it with &lt;code&gt;phpize&lt;/code&gt; and &lt;code&gt;make&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Adding a line to &lt;code&gt;php.ini&lt;/code&gt; to enable it.&lt;/li&gt;
&lt;li&gt;Restarting PHP-FPM or Apache to load it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This workflow makes extensions harder to distribute and standardize compared to Composer packages.&lt;/p&gt;
&lt;h3&gt;
  
  
  PECL
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clunky and outdated.&lt;/li&gt;
&lt;li&gt;Slow to install.&lt;/li&gt;
&lt;li&gt;Lacks proper security (no package signing).&lt;/li&gt;
&lt;li&gt;Not officially backed by PHP, and some in the community want to phase it out.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  docker-php-extension-installer
&lt;/h3&gt;

&lt;p&gt;A widely used community project that simplifies extension installation inside Docker images.&lt;br&gt;&lt;br&gt;
Instead of writing complex &lt;code&gt;apt-get&lt;/code&gt; + &lt;code&gt;phpize&lt;/code&gt; + &lt;code&gt;make&lt;/code&gt; commands, you just add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ghcr.io/mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;install-php-extensions xdebug redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is great, but still not perfect—it remains Docker-specific and doesn’t integrate with Composer or Packagist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project to replace PECL
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;pie-design&lt;/strong&gt; repository defines the foundations of &lt;strong&gt;PIE&lt;/strong&gt;, a new way to install extensions as easily as PHP packages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Started in March 2024.&lt;/li&gt;
&lt;li&gt;Version 1 released in June 2025.&lt;/li&gt;
&lt;li&gt;PIE is distributed as a single &lt;code&gt;phar&lt;/code&gt; file: just download it and use it.&lt;/li&gt;
&lt;li&gt;All extension metadata is stored in Packagist.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Command options
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pie install ext-xdebug&lt;/code&gt; → installs an extension and updates &lt;code&gt;php.ini&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pie uninstall ext-redis&lt;/code&gt; → removes an extension.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pie update&lt;/code&gt; → upgrades to the latest available version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pie search redis&lt;/code&gt; → searches for extensions in Packagist.&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;pie&lt;/code&gt; without arguments reads extensions from &lt;code&gt;composer.json&lt;/code&gt; and installs them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add repositories via Composer, VCS, or local paths.&lt;/li&gt;
&lt;li&gt;Automatic &lt;code&gt;php.ini&lt;/code&gt; update.&lt;/li&gt;
&lt;li&gt;Support for &lt;code&gt;GH_TOKEN&lt;/code&gt; to install from private repositories.&lt;/li&gt;
&lt;li&gt;OS compatibility restrictions.&lt;/li&gt;
&lt;li&gt;Symfony CLI integration: &lt;code&gt;symfony pie install&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The future of extensions
&lt;/h3&gt;

&lt;p&gt;PIE is the theoretical replacement for PECL.&lt;br&gt;&lt;br&gt;
An RFC vote was held, closing on &lt;strong&gt;September 20, 2025&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Almost everyone voted &lt;em&gt;yes&lt;/em&gt;, which means PIE is now the official successor to PECL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Make your devs happy by normalizing your API errors (Clément Herreman)
&lt;/h2&gt;

&lt;p&gt;Errors are not just bugs. They’re an opportunity to give users autonomy through clear feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an error?
&lt;/h3&gt;

&lt;p&gt;An error is any behavior—intentional or not—that prevents the user from completing their task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why normalize errors?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To react properly to a precise issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrying a token.&lt;/li&gt;
&lt;li&gt;Handling distributed system failures.&lt;/li&gt;
&lt;li&gt;Fixing configuration issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To present errors consistently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear and understandable messages for end-users.&lt;/li&gt;
&lt;li&gt;Precise identification to ease support.&lt;/li&gt;
&lt;li&gt;Keeping some details vague for security reasons.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How?
&lt;/h3&gt;

&lt;p&gt;Errors can be classified into three categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Errors that belong to your domain: you own them, so enrich them with context.&lt;/li&gt;
&lt;li&gt;Errors that don’t belong to your domain but still happen: wrap them with a code and enrich them.&lt;/li&gt;
&lt;li&gt;Rare/unexpected errors: keep the default JSON output.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  RFC 7807: Problem Details for HTTP APIs
&lt;/h4&gt;

&lt;p&gt;This RFC defines a standard JSON structure for errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt;: unique machine-readable code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt;: short, human-readable summary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;detail&lt;/code&gt;: contextual explanation of this particular error.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instance&lt;/code&gt;: URL to the error catalog.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;...&lt;/code&gt;: any custom fields you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Example HTTP response
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt; &lt;span class="ne"&gt;Unauthorized&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/problem+json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/errors/authentication_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authentication failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your token has expired. Please request a new one."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/login"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  API Platform
&lt;/h4&gt;

&lt;p&gt;API Platform provides a ready-to-use &lt;code&gt;ApiPlatform\Problem\Error&lt;/code&gt; class to implement RFC 7807.&lt;/p&gt;

&lt;h4&gt;
  
  
  Organizing errors
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Keep only business exceptions in the domain layer.&lt;/li&gt;
&lt;li&gt;Wrap infrastructure errors before sending them to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Documenting errors
&lt;/h4&gt;

&lt;p&gt;Errors can be declared as attributes on operations, making them explicit in the API docs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Improvements: RFC 9457
&lt;/h4&gt;

&lt;p&gt;RFC 9457 is essentially the same as RFC 7807, with some additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A registry of errors via &lt;code&gt;schema.org&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A mechanism for returning multiple errors at once (though strongly discouraged).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Clément highlighted: RFC 9457 doesn’t bring much practical value, and some of its suggestions are even discouraged in the spec.&lt;/p&gt;




&lt;h2&gt;
  
  
  Symfony and Dependency Injection: From past to future (Imen Ezzine)
&lt;/h2&gt;

&lt;p&gt;Dependency Injection (DI) is the “D” in SOLID, and it has been a cornerstone of Symfony’s design for nearly two decades.&lt;br&gt;&lt;br&gt;
This talk explored its history, evolution, and what’s next.&lt;/p&gt;

&lt;h3&gt;
  
  
  The early days
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;2007 – Symfony 1&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Services instantiated directly, often via &lt;code&gt;sfContext()&lt;/code&gt; (a singleton).&lt;/li&gt;
&lt;li&gt;Hard to test, rigid, tightly coupled.&lt;/li&gt;
&lt;li&gt;No real container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Symfony 2 and the paradigm shift
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;2011 – Symfony 2&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction of a central container.&lt;/li&gt;
&lt;li&gt;Services configured via YAML and parameters.&lt;/li&gt;
&lt;li&gt;Dependencies injected as constructor arguments.&lt;/li&gt;
&lt;li&gt;Autowiring introduced in &lt;strong&gt;Symfony 2.8&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2015 – API Platform v1&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heavy reliance on autowiring (then experimental).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2016 – API Platform v2&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@ApiResource&lt;/code&gt; annotation magic powered by the DI component.&lt;/li&gt;
&lt;li&gt;Data persisters and providers had to be tagged manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2017 – Symfony 3.3 / API Platform 2.2&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autowiring + autoconfigure.&lt;/li&gt;
&lt;li&gt;Manual tagging mostly eliminated (providers/persisters automatically wired).&lt;/li&gt;
&lt;li&gt;Symfony 3.4: services private by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Symfony 5 to Symfony 7
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;2021 – Symfony 5.3&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DI powered by attributes → much less YAML.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[When]&lt;/code&gt; attribute for conditional services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Symfony 6.0 – 6.3&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New attributes for corner cases.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[Autowire]&lt;/code&gt; attribute for precise service injection.&lt;/li&gt;
&lt;li&gt;Support for env vars and parameters via attributes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[AsAlias]&lt;/code&gt; to alias services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2022 – API Platform 3.0&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New state processors and providers replace older persister/provider pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2023 – Symfony 7&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;#[AutoconfigureTag]&lt;/code&gt; → automatic tagging (used in API Platform filters).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TaggedIterator&lt;/code&gt; → inject multiple tagged services.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AutowireIterator&lt;/code&gt; → autowire all classes implementing an interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Symfony 7.1 – 7.3&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;#[AutowireMethodOf]&lt;/code&gt; to autowire a single method.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[WhenNot]&lt;/code&gt; for conditional services.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;when&lt;/code&gt; parameter in &lt;code&gt;#[AsAlias]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;p&gt;Over 20 years, DI in Symfony evolved from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual instantiation →&lt;/li&gt;
&lt;li&gt;Manual configuration →&lt;/li&gt;
&lt;li&gt;Automatic configuration through &lt;strong&gt;attributes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This journey has made Symfony projects &lt;strong&gt;more testable, maintainable, and developer-friendly&lt;/strong&gt; while reducing boilerplate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Cover image by &lt;a href="https://ncls.tv/" rel="noopener noreferrer"&gt;Nicolas Detrez&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>techtalks</category>
      <category>api</category>
      <category>symfony</category>
    </item>
    <item>
      <title>API Platform Con 2025 - Day 1</title>
      <dc:creator>Thérage Kévin</dc:creator>
      <pubDate>Wed, 24 Sep 2025 07:52:30 +0000</pubDate>
      <link>https://dev.to/sensiolabs/api-platform-con-2025-day-1-1gpo</link>
      <guid>https://dev.to/sensiolabs/api-platform-con-2025-day-1-1gpo</guid>
      <description>&lt;p&gt;I had the opportunity to attend the API Platform Con 2025 thanks to SensioLabs and here is what I learned through the talks I viewed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enhance your API Platform APIs with Go thanks to FrankenPHP (Kévin Dunglas)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Slides of this talk are available : &lt;a href="https://dunglas.dev/2025/09/the-best-of-both-worlds-go-powered-grpc-for-your-php-and-api-platform-apps/" rel="noopener noreferrer"&gt;https://dunglas.dev/2025/09/the-best-of-both-worlds-go-powered-grpc-for-your-php-and-api-platform-apps/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;API Platform is celebrating its 10th anniversary this year, having been created on January 20, 2015. Originally a Symfony bundle, it is now usable with Laravel or even without any framework. With over 14,000 stars on GitHub and 921 code and documentation contributors, API Platform has become an essential tool for creating APIs.&lt;/p&gt;

&lt;p&gt;Kevin highlighted that it has also been the starting point for many related projects such as Mercure and FrankenPHP, and many Symfony components were first developed for API Platform.&lt;/p&gt;

&lt;p&gt;He also paid tribute to Ryan Weaver, a key contributor, and encouraged attendees to support his family through the &lt;a href="https://gofund.me/31ec53011" rel="noopener noreferrer"&gt;GoFundMe "In memory of Ryan Weaver: For his son Beckett"&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Model, Many API Architecture Types
&lt;/h3&gt;

&lt;p&gt;With API Platform, you can use the same DTO, the same code, and the same PHP class to generate different output formats with just a few configuration changes.&lt;/p&gt;

&lt;p&gt;Here are some of the supported formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hydra&lt;/li&gt;
&lt;li&gt;OpenAPI&lt;/li&gt;
&lt;li&gt;HAL&lt;/li&gt;
&lt;li&gt;JSON:API&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;li&gt;Mercure (SSE support)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach eliminates code duplication across different API format requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why gRPC is missing in API Platform
&lt;/h3&gt;

&lt;p&gt;Currently, gRPC is not supported by API Platform. Here's why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gRPC does not follow REST principles.&lt;/li&gt;
&lt;li&gt;It is different from GraphQL.&lt;/li&gt;
&lt;li&gt;It uses Protobuf (a binary format) instead of JSON for the output format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time, in classic gRPC architecture, PHP is not a candidate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aa574oibcb89hyrtjmm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aa574oibcb89hyrtjmm.jpg" title="Schema of Typical gRPC Architecture which does not include a PHP side gRPC server but a C++ server, android/java client and a ruby client" alt="Schema of Typical gRPC Architecture which does not include a PHP side gRPC server but a C++ server, android/java client and a ruby client" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, gRPC has several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast and efficient&lt;/li&gt;
&lt;li&gt;Strongly typed&lt;/li&gt;
&lt;li&gt;Language agnostic: a code generator allows generating data structures in many languages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use cases for gRPC include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microservices&lt;/li&gt;
&lt;li&gt;Internet of Things (IoT)&lt;/li&gt;
&lt;li&gt;Critical components where performance is essential&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How gRPC Works
&lt;/h3&gt;

&lt;p&gt;gRPC operates on HTTP/2 and uses Protocol Buffer &lt;code&gt;.proto&lt;/code&gt; files to define service contracts. These definitions enable automatic code generation across multiple programming languages. The binary serialization format provides more efficient data transmission than JSON, while HTTP/2's multiplexing supports high-performance communication.&lt;/p&gt;

&lt;p&gt;Moreover, the official gRPC documentation recommends using non-PHP languages for gRPC servers due to PHP-FPM's request lifecycle limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  gRPC with FrankenPHP
&lt;/h3&gt;

&lt;p&gt;Thankfully, FrankenPHP offers a way to write extensions in Go that can be exposed in PHP, making it possible to use gRPC with API Platform. The FrankenPHP gRPC extension is available on GitHub and is testable. It uses the Go gRPC server and is designed to be used with or without API Platform.&lt;/p&gt;

&lt;p&gt;For more information on configuration and usage of this extension, please refer to the &lt;a href="https://github.com/dunglas/frankenphp-grpc" rel="noopener noreferrer"&gt;GitHub repository documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Testing can be done with gRPCui. It is important to note that this solution is still experimental.&lt;/p&gt;




&lt;h2&gt;
  
  
  Extend Caddy Web Server with Your Favorite Language (Sylvain Combraque)
&lt;/h2&gt;

&lt;p&gt;Caddy is a modern, fast, and easy-to-use web server that simplifies the process of serving websites and web applications. It is known for its automatic HTTPS configuration and simple syntax. Matt Holt, the creator of Caddy, has made significant contributions to the web server landscape with Caddy's unique features and ease of use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extending Caddy
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Using xcaddy Build
&lt;/h4&gt;

&lt;p&gt;Extending Caddy can be done using the &lt;code&gt;xcaddy&lt;/code&gt; build tool, which allows you to customize and extend Caddy with plugins written in Go. This tool provides a straightforward way to add new functionalities to Caddy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using WebUI from Caddy Website
&lt;/h4&gt;

&lt;p&gt;Caddy also offers a WebUI that can be accessed from the Caddy website. This interface provides an easy way to manage and configure your Caddy web server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extending Caddy with Go
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz8xh7oa7mvftlsldnjl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz8xh7oa7mvftlsldnjl.jpg" title="An example of a Caddy extension made with GO" alt="An example of a Caddy extension made with GO" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more detailed information on how to extend Caddy with Go, you can refer to the &lt;a href="https://caddyserver.com/docs/extending-caddy" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Interpreter
&lt;/h3&gt;

&lt;p&gt;While using interpreters to extend Caddy has its advantages, there are also some drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need one interpreter per language.&lt;/li&gt;
&lt;li&gt;New versions of the language require new interpreters.&lt;/li&gt;
&lt;li&gt;Each interpreter is maintained separately.&lt;/li&gt;
&lt;li&gt;You may need to re-implement types.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WASM x WASI x darkweak/wazemmes for Caddy Extension in Any Language
&lt;/h3&gt;

&lt;p&gt;WebAssembly (WASM) is a binary instruction format that promises to enable programs to run at near-native speed on the web. The promise of "build once, run everywhere" makes WASM an attractive option for extending Caddy. However, the current documentation is not user-friendly, and there are some bugs to be aware of.&lt;/p&gt;

&lt;p&gt;WebAssembly System Interface (WASI) is a system interface designed to allow WebAssembly modules to interact with the operating system in a secure and portable way. This combination of WASM and WASI allows developers to write code in their preferred language and compile it to WASM for execution in a browser or server environment.&lt;/p&gt;

&lt;p&gt;For more information on using WASM and WASI in Caddy, you can check out the &lt;a href="https://github.com/darkweak/wazemmes" rel="noopener noreferrer"&gt;darkweak/wazemmes repository on GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mercure, SSE, API Platform and an LLM Elevate a Chat(bot) (Mathieu Santostefano)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Slides of this talk are available : &lt;a href="https://welcomattic.github.io/slides-real-time-ai-chatbot-with-mercure/1" rel="noopener noreferrer"&gt;https://welcomattic.github.io/slides-real-time-ai-chatbot-with-mercure/1&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Origin of the Subject
&lt;/h3&gt;

&lt;p&gt;The initial customer need was to create paid expert chat exchanges. The first version used an API + React, but lacked message history. Mercure was chosen for secure message distribution to customers via JWT.&lt;/p&gt;

&lt;p&gt;Evolved needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assist experts with an AI assistant&lt;/li&gt;
&lt;li&gt;Allow AI to handle the first part of the conversation&lt;/li&gt;
&lt;li&gt;Allow experts to take over when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Toolbox
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Mercure - Real-time Exchanges
&lt;/h4&gt;

&lt;p&gt;Architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server =&amp;gt; Hub =&amp;gt; Client&lt;/li&gt;
&lt;li&gt;Client =&amp;gt; Hub =&amp;gt; Client&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  SSE
&lt;/h4&gt;

&lt;p&gt;Server-Sent Events (SSE) is a technology that allows a server to send real-time updates to a client via a persistent HTTP connection. The client listens to a stream of events sent by the server.&lt;/p&gt;

&lt;h4&gt;
  
  
  API Platform
&lt;/h4&gt;

&lt;h5&gt;
  
  
  LLM
&lt;/h5&gt;

&lt;p&gt;Data generation and intelligent responses&lt;/p&gt;

&lt;h5&gt;
  
  
  Symfony Messenger
&lt;/h5&gt;

&lt;p&gt;Asynchronous process management&lt;/p&gt;

&lt;h5&gt;
  
  
  Symfony AI
&lt;/h5&gt;

&lt;p&gt;Equivalent to Mailer/Notifier but for AI providers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Platform&lt;/strong&gt;: unified interface for all AI providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent&lt;/strong&gt;: agentic AI creation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store&lt;/strong&gt;: data storage abstraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP SDK&lt;/strong&gt;: now officially supported by Anthropic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Bundle&lt;/strong&gt;: full integration with Symfony&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Bundle&lt;/strong&gt;: additional components&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Technical flow:&lt;br&gt;
&lt;code&gt;User =&amp;gt; message =&amp;gt; Mercure (Storage) &amp;lt;= Symfony SSE client =&amp;gt; Symfony Messenger =&amp;gt; Mistral =&amp;gt; Mercure =&amp;gt; AI response =&amp;gt; User&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Secure private chat via JWT (JSON Web Tokens) - an open standard for securely exchanging data between parties.&lt;/p&gt;
&lt;h4&gt;
  
  
  Sending a message to Mercure
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hubURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Send JWT cookie&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MercureUpdateData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;conversationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;private&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// restrict message to subscribed clients&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Connecting to Mercure
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sse-endpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New event received: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Symfony SSE Client
&lt;/h4&gt;

&lt;p&gt;Built-in EventSourceHttpClient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpClient\Chunk\ServerSentEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpClient\EventSourceHttpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpClient\HttpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$eventSourceClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSourceHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nv"&gt;$connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$eventSourceClient&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"YOUR-MERCURE-URL"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$eventSourceClient&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isTimeout&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Keep the connection alive.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isLast&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Connection closed by server.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ServerSentEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;processSSE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Dispatching messages with Messenger
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpClient\Chunk\ServerSentEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;processSSE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ServerSentEvent&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getArrayData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// do some checks before asking LLM&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;shouldProcessWithAi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Dispacth message to ask LLM&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;messageBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProcessAiResponseMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;conversationId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'conversationId'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;userMessage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;sseMessageId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'timestamp'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Symfony AI Configuration
&lt;/h4&gt;

&lt;h5&gt;
  
  
  YAML configuration of AI bundle with Mistral
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;mistral&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(MISTRAL_API_KEY)%'&lt;/span&gt;

    &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;symfony_ai.platform.mistral'&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Symfony\AI\Platform\Bridge\Mistral\Mistral'&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!php/const&lt;/span&gt; &lt;span class="s"&gt;Symfony\AI\Platform\Bridge\Mistral\Mistral::MISTRAL_LARGE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Handler Example
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Symfony AI Bundle code example&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\AI\Agent\AgentInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\AI\Agent\Chat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\AI\Platform\Message\Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\AI\Platform\Message\MessageBag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\AI\Store\StoreInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;AgentInterface&lt;/span&gt; &lt;span class="nv"&gt;$agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;StoreInterface&lt;/span&gt; &lt;span class="nv"&gt;$messageStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ProcessAiResponseMessage&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;messageStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"UNIQUE_ID_TO_PROMPT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;messageStore&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"UNIQUE_ID_TO_PROMPT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messages&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// retrieve system prompt from somewhere ...&lt;/span&gt;

            &lt;span class="c1"&gt;// Programmatic System prompt injection&lt;/span&gt;
            &lt;span class="nv"&gt;$chat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;initiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MessageBag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SYSTEM_PROMPT_INJECTION"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$llmAnswer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$chat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ofUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// do something with the answer&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In his final words Mathieu dedicated his talk in memory of Ryan Weaver, whose contributions continue to inspire the Symfony community. He also thanked Christopher Hertel for the Symfony AI initiative.&lt;/p&gt;




&lt;h2&gt;
  
  
  How API Platform 4.2 is Redefining API Development (Antoine Bluchet)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Slides of this talk are available : &lt;a href="https://soyuka.me/api-platform-4-2-redefining-api-development/" rel="noopener noreferrer"&gt;https://soyuka.me/api-platform-4-2-redefining-api-development/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Looking back at version 4.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;610 commits&lt;/li&gt;
&lt;li&gt;~200,000 lines of code&lt;/li&gt;
&lt;li&gt;291 issues opened&lt;/li&gt;
&lt;li&gt;230 issues closed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's New in 4.2
&lt;/h3&gt;

&lt;p&gt;Key features of this release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FrankenPHP integration&lt;/li&gt;
&lt;li&gt;State Options&lt;/li&gt;
&lt;li&gt;Query parameters enhancements&lt;/li&gt;
&lt;li&gt;Performance improvements&lt;/li&gt;
&lt;li&gt;Laravel compatibility&lt;/li&gt;
&lt;li&gt;PHP File Metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Metadata Enhancements
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Metadata from PHP Files
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1z9cw9en3mn20iwvvpe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1z9cw9en3mn20iwvvpe.jpg" title="An example of a PHP file metadata" alt="An example of a PHP file metadata" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New metadata system allows extracting API configuration directly from PHP files. It is not documented yet (AFAIK) but you can see the related PR of Loïc Frémont &lt;a href="https://github.com/api-platform/core/pull/7017" rel="noopener noreferrer"&gt;https://github.com/api-platform/core/pull/7017&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Metadata Mutator
&lt;/h4&gt;

&lt;p&gt;A new way to programmatically modify metadata:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More flexible configuration&lt;/li&gt;
&lt;li&gt;Runtime adjustments&lt;/li&gt;
&lt;li&gt;Cleaner architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  From API Filter to Parameters
&lt;/h3&gt;

&lt;h4&gt;
  
  
  API Filter Retrospective
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;#[ApiFilter]&lt;/code&gt; attribute was doing a lot of things in the background, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declare services with filter tags&lt;/li&gt;
&lt;li&gt;Generate documentation&lt;/li&gt;
&lt;li&gt;Apply database operations&lt;/li&gt;
&lt;li&gt;Work with multiple properties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was confusing and also not respecting Single Responsibility Principle. That's the reason why API Platform maintainers have decided to rework that to Parameters.&lt;/p&gt;

&lt;h4&gt;
  
  
  Filter Documentation Improvements
&lt;/h4&gt;

&lt;p&gt;Now documentations are generated separately. This can be done with two new interfaces&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JsonSchemaFilterInterface&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OpenApiParameterFilter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Filtering System
&lt;/h4&gt;

&lt;p&gt;Now Filter are independent through a new &lt;code&gt;FilterInterface&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplified &lt;code&gt;apply()&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;No constructor requirements&lt;/li&gt;
&lt;li&gt;Dependency-free design&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Parameter System
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;#[ApiFilter]&lt;/code&gt; attribute will leave his place to a new property of Operations attributes called &lt;code&gt;parameters&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F733nj3gdwsl82qpcgqvs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F733nj3gdwsl82qpcgqvs.jpg" title="An example of Parameters usage" alt="An example of Parameters usage" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  New Filter Types
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Free text search capabilities&lt;/li&gt;
&lt;li&gt;URI variable provider&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JSON Schema Enhancements
&lt;/h3&gt;

&lt;p&gt;Some improvements were made on JSON Schema generation. Those changes could imply a backward compatibility break for tools using the former JSON Schema.&lt;/p&gt;

&lt;p&gt;Improvements&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema mutualization&lt;/li&gt;
&lt;li&gt;30% smaller OpenAPI specification files&lt;/li&gt;
&lt;li&gt;Reduced I/O operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A new tool is now recommended : &lt;a href="https://pb33f.io" rel="noopener noreferrer"&gt;pb33f.io&lt;/a&gt; as it is more feature-rich and better maintained than Swagger UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;Performance benchmarks comparing Nginx vs FrankenPHP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca88dv38n1sjiuql8sfq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca88dv38n1sjiuql8sfq.jpg" title="Performance comparison between Nginx and FrankenPHP 1" alt="Performance comparison between Nginx and FrankenPHP 1" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nmnf07glk5uz0z4qt0o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nmnf07glk5uz0z4qt0o.jpg" title="Performance comparison between Nginx and FrankenPHP 2" alt="Performance comparison between Nginx and FrankenPHP 2" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More benchmarks available at &lt;a href="https://soyuka.github.io/sylius-benchmarks/" rel="noopener noreferrer"&gt;soyuka.github.io/sylius-benchmarks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JSON Streamer improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~32.4% better request/second performance&lt;/li&gt;
&lt;li&gt;Configurable via settings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State Options
&lt;/h3&gt;

&lt;p&gt;New features for querying subresources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More efficient data loading&lt;/li&gt;
&lt;li&gt;Entity class magic (RIP Ryan)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data Mapping
&lt;/h3&gt;

&lt;p&gt;New mapping capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database to API representation mapping&lt;/li&gt;
&lt;li&gt;Symfony ObjectMapper integration&lt;/li&gt;
&lt;li&gt;Better data transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;Profiling tools are back!&lt;/p&gt;

&lt;h3&gt;
  
  
  Backward Compatibility
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Many new features added&lt;/li&gt;
&lt;li&gt;No deprecations in this version&lt;/li&gt;
&lt;li&gt;Parameters system is no longer experimental&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Looking Ahead to API Platform 5.0
&lt;/h3&gt;

&lt;p&gt;Planned changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;#[ApiFilter]&lt;/code&gt; deprecation (migration script coming soon)&lt;/li&gt;
&lt;li&gt;More JSON Streamer usage&lt;/li&gt;
&lt;li&gt;Object Mapper feature requests&lt;/li&gt;
&lt;li&gt;Community-driven improvements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Design pattern the treasure is in the vendor (Smaïne Milianni)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Slides of this talk are available : &lt;a href="https://ismail1432.github.io/conferences/2025/apip_con/index.html" rel="noopener noreferrer"&gt;https://ismail1432.github.io/conferences/2025/apip_con/index.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this talk Smaïne made a tour on Design Pattern that are commonly used without any knowledge that they are in the vendors we use on a daily basis. He also showcased small and comprehensible code PHP snippets explaining some of them.&lt;/p&gt;

&lt;p&gt;Among them were :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Strategy Pattern&lt;/li&gt;
&lt;li&gt;The Adapter Pattern&lt;/li&gt;
&lt;li&gt;The Factory Pattern&lt;/li&gt;
&lt;li&gt;The Builder Pattern&lt;/li&gt;
&lt;li&gt;The Proxy Pattern&lt;/li&gt;
&lt;li&gt;The Observer Pattern&lt;/li&gt;
&lt;li&gt;The Event Dispatcher Pattern&lt;/li&gt;
&lt;li&gt;The Decorator Pattern&lt;/li&gt;
&lt;li&gt;The Facade Pattern&lt;/li&gt;
&lt;li&gt;The Template Pattern&lt;/li&gt;
&lt;li&gt;The Chain of Responsibility Pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smaïne also ended with a shoutout to Ryan Weaver.&lt;/p&gt;




&lt;h2&gt;
  
  
  What if we do Event Storming in our API Platform projects ? (Gregory Planchat)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Event Storming
&lt;/h3&gt;

&lt;p&gt;Event Storming is a collaborative workshop technique that brings together both users and developers in the same room. This methodology shines a light on misunderstandings that often exist between business stakeholders and technical teams.&lt;/p&gt;

&lt;p&gt;The beauty of Event Storming lies in its simplicity: it uses physical post-it notes to encourage different team members to exchange ideas, see each other, share knowledge, meet face-to-face, and confront their understanding of the business domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;

&lt;p&gt;The Event Storming process follows a structured approach with several key steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;List the events&lt;/strong&gt; - Start by identifying all the significant events that happen in your business domain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organize the events&lt;/strong&gt; - Arrange these events in a chronological or logical order&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up the commands&lt;/strong&gt; - Identify what actions trigger each event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up the actors&lt;/strong&gt; - Determine who or what initiates each command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green post-its&lt;/strong&gt; - Add the data necessary for users to make decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add external systems&lt;/strong&gt; - Include third-party systems that interact with your domain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregates&lt;/strong&gt; - Group related events and commands into cohesive business concepts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The team mentioned that they conducted multiple sessions "until no one had any more questions, whether from the technical team or the business team." It's a self-documenting process that can be repeated as the business evolves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;p&gt;Event Storming brings several concrete benefits to development teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process documentation&lt;/strong&gt; - The workshop naturally creates living documentation of your business processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilitated onboarding&lt;/strong&gt; - New team members can quickly understand the domain by looking at the Event Storming artifacts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reveals uncertainties&lt;/strong&gt; - Hidden assumptions and unclear requirements surface during the collaborative sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  With API Platform
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The Anemic Model Problem
&lt;/h4&gt;

&lt;p&gt;Most applications suffer from what's called the anemic model anti-pattern, where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business logic is scattered across numerous services&lt;/li&gt;
&lt;li&gt;Loss of user intention tracking&lt;/li&gt;
&lt;li&gt;Entities become mere data containers with getters and setters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Typically, when you want to modify information in your entity, you call a setter method. This often happens across multiple services and classes, hence the "business logic disseminated in numerous services" problem.&lt;/p&gt;

&lt;p&gt;The intention is essential for third-party systems to understand what actually happened in your application.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rich Models
&lt;/h4&gt;

&lt;p&gt;The alternative approach uses rich domain models that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Require significant cost&lt;/strong&gt; - More complex to implement initially&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Require detailed application understanding&lt;/strong&gt; - Team needs deep domain knowledge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guarantee consistency over time&lt;/strong&gt; - Business rules are enforced at the model level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply business constraints&lt;/strong&gt; - Validation logic lives where it belongs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralize business logic&lt;/strong&gt; - Everything related to a concept lives in one or two classes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model guarantees integrity&lt;/strong&gt; - Invalid states become impossible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a rich model, all changes happen within the entity itself, keeping the business logic centralized and coherent.&lt;/p&gt;

&lt;h4&gt;
  
  
  The CRUD Problem
&lt;/h4&gt;

&lt;p&gt;Traditional CRUD operations are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited to 4 operations (Create, Read, Update, Delete)&lt;/li&gt;
&lt;li&gt;SQL-centric thinking&lt;/li&gt;
&lt;li&gt;Tools like PostgREST generate REST APIs automatically but provide little business value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do better, we can leverage the power of API Platform's State Providers and State Processors.&lt;/p&gt;

&lt;p&gt;But how do we preserve intention in our application?&lt;/p&gt;

&lt;p&gt;The solution follows this flow:&lt;br&gt;
&lt;strong&gt;Repository → EventBus → Event → Handler&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Model Modification
&lt;/h4&gt;

&lt;p&gt;The team implemented a pattern with three key methods in their entities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;recordThat()&lt;/strong&gt; - Records that an event occurred (e.g., "a deployment was launched")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;apply()&lt;/strong&gt; - Applies the modifications related to the event (e.g., updates the deployment date)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;releaseEvents()&lt;/strong&gt; - A cleanup step that happens during the save process, just before persist/flush, then dispatches events throughout the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach ensures that every business action is captured as a meaningful event, preserving the user's intention and providing a clear audit trail of what happened in the system.&lt;/p&gt;
&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;The team reported several concrete improvements after implementing this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;An API and codebase that better resembled the company's business domain&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User intention was preserved&lt;/strong&gt; throughout the application lifecycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better understanding of actions performed&lt;/strong&gt; in the application, both for developers and business stakeholders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c6ciqape365oj0bo5vv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c6ciqape365oj0bo5vv.jpg" title="Abstract of the OpenAPI documentation of an Event Stormed done API" alt="Abstract of the OpenAPI documentation of an Event Stormed done API" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Scaling Databases (Tobias Petry)
&lt;/h2&gt;

&lt;p&gt;Tobias Petry shared insights on database scaling strategies. His talk highlighted why scalability issues usually originate at the database level and walked through the most common solutions, their advantages, and their pitfalls.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;

&lt;p&gt;There is no silver bullet: every application has its own constraints. Still, several well-known strategies exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Find and fix slow queries&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Before considering infrastructure, always check the basics. Tools like &lt;a href="https://mysqlexplain.com" rel="noopener noreferrer"&gt;mysqlexplain.com&lt;/a&gt; can help detect inefficient queries and suggest improvements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cache results&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Serving cached responses drastically reduces the load on the database and avoids repeating costly operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vertical scaling (bigger machines)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Sometimes the simplest option is to scale up: move the database to a more powerful server. However, this approach quickly reaches physical and financial limits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-master replication&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In this setup, several servers accept both reads and writes. It improves write scalability but creates the risk of conflicts when parallel writes occur. Conflict resolution strategies can mitigate this, but complexity grows with the number of nodes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Read replication&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Here, a single primary node handles writes, while replicas serve read queries.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous replication&lt;/strong&gt; ensures that changes are propagated to all replicas before acknowledging the write. This guarantees consistency but adds latency, as every replica must confirm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous replication&lt;/strong&gt; acknowledges the write immediately and updates replicas later. It reduces latency but risks temporary inconsistency between nodes.
In practice, most applications tolerate eventual consistency. A cache layer in front of the primary often hides replication lag. Still, studies show that 90–98% of applications encounter latency issues if relying only on replicas for reads.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sharding&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Sharding distributes data across multiple databases. This enables &lt;em&gt;theoretically infinite scalability&lt;/em&gt;. For example, users might be split across shards based on their ID.&lt;br&gt;&lt;br&gt;
The challenge comes with cross-shard queries: if you need to fetch all orders of a user across multiple shops, and users and shops are sharded differently, you must query several shards and aggregate results manually. Some companies even introduce &lt;em&gt;shards of shards&lt;/em&gt;, adding another layer of complexity.&lt;br&gt;&lt;br&gt;
Because of this overhead, sharding is usually reserved for very large-scale systems. For most use cases, read replication is sufficient.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general, these strategies are designed for CRUD workloads. Analytical queries (dashboards, reports) are harder to scale with a standard relational database. Developers can explore resources like &lt;a href="https://sqlfordevs.com" rel="noopener noreferrer"&gt;sqlfordevs.com&lt;/a&gt; (free course on making analytics faster) or specialized systems such as &lt;a href="https://www.timescale.com" rel="noopener noreferrer"&gt;TimescaleDB&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sounds complicated
&lt;/h3&gt;

&lt;p&gt;Tobias emphasized a crucial point: scaling decisions must be made before hitting database bottlenecks. Once data is structured and scaling strategies are in place, rolling back becomes almost impossible. Database architecture is one of those areas where it is far easier to make the right decision early than to correct mistakes later.&lt;/p&gt;


&lt;h2&gt;
  
  
  API Platform, JsonStreamer and ESA for skyrocketing API (Mathias Arlaud)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Slides of this talk are available : &lt;a href="https://www.canva.com/design/DAGyYPxkygw/M1RzOiv8_cMp0Pa7Mh0u4g/view" rel="noopener noreferrer"&gt;https://www.canva.com/design/DAGyYPxkygw/M1RzOiv8_cMp0Pa7Mh0u4g/view&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Storytelling: imagine a bookstore. A customer orders &lt;strong&gt;all&lt;/strong&gt; Symfony-related books. The bookseller tries to gather them all, but it's heavy—takes time, lots of books. The second time, the same request, but the pile is so large that the bookseller collapses under the weight.&lt;/p&gt;

&lt;p&gt;In the API world, &lt;strong&gt;JSON is king&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the heart of our stack is &lt;strong&gt;API Platform&lt;/strong&gt;, which relies on Symfony’s Serializer. But sometimes the Serializer is like that bookseller: it works well until the load becomes too heavy.&lt;/p&gt;
&lt;h3&gt;
  
  
  Serialization / Normalization in Symfony
&lt;/h3&gt;

&lt;p&gt;Serialization in Symfony (and in API Platform) involves turning PHP objects into arrays or scalar values, then encoding to formats like JSON or XML. &lt;strong&gt;Normalization&lt;/strong&gt; transforms the internal object graph into a neutral data structure (arrays, scalars), applying metadata such as groups or attributes. &lt;strong&gt;Encoding&lt;/strong&gt; then converts that structure into the final JSON string. The reverse process (&lt;strong&gt;denormalization&lt;/strong&gt;) handles input JSON → arrays → objects.&lt;/p&gt;

&lt;p&gt;When objects or collections are small, this works fine. But with thousands of items, large graphs, deep associations, and nested arrays, memory usage and time-to-first-byte degrade. Serialization becomes a bottleneck.&lt;/p&gt;
&lt;h3&gt;
  
  
  Streaming as a solution
&lt;/h3&gt;

&lt;p&gt;Instead of building a huge in-memory structure, streaming emits JSON pieces &lt;strong&gt;incrementally&lt;/strong&gt;. You only keep in memory what’s necessary at each moment.&lt;/p&gt;

&lt;p&gt;Symfony 7.3 introduces the &lt;strong&gt;JsonStreamer&lt;/strong&gt; component for that purpose.&lt;/p&gt;

&lt;p&gt;Some key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works best with &lt;strong&gt;POPOs&lt;/strong&gt; (Plain Old PHP Objects) having public properties, without complex constructors.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;#[JsonStreamable]&lt;/code&gt; attribute can be used on classes to mark them as streamable. This also allows pre-generation of code during cache warm-up.&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;TypeInfo&lt;/strong&gt; component (&lt;a href="https://symfony.com/blog/new-in-symfony-7-3-jsonstreamer-component" rel="noopener noreferrer"&gt;link&lt;/a&gt;) to describe types of collections and objects (e.g., &lt;code&gt;Type::list(Type::object(MyDto::class))&lt;/code&gt;). This helps JsonStreamer guess the shape of the output JSON without loading everything in memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a code snippet from the Symfony documentation showing basic usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example class&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Dto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\JsonStreamer\Attribute\JsonStreamable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[JsonStreamable]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In controller&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\JsonStreamer\StreamWriterInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\TypeInfo\Type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\StreamedResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;retrieveUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StreamWriterInterface&lt;/span&gt; &lt;span class="nv"&gt;$jsonStreamWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;UserRepository&lt;/span&gt; &lt;span class="nv"&gt;$userRepository&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;StreamedResponse&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$userRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$jsonStreamWriter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamedResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benchmarks &amp;amp; comparisons&lt;/p&gt;

&lt;p&gt;For a dataset of 10,000 objects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Memory usage / footprint (rough / relative)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Serializer (traditional)&lt;/td&gt;
&lt;td&gt;~ 204 ms&lt;/td&gt;
&lt;td&gt;~ 16 MB (grows with size)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JsonStreamer&lt;/td&gt;
&lt;td&gt;~ 87 ms&lt;/td&gt;
&lt;td&gt;~ 8 MB (much more constant)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Challenges with metadata, JSON-LD and how API Platform adapts&lt;/p&gt;

&lt;p&gt;API Platform adds metadata, JSON-LD contexts, property metadata, etc. That adds overhead in serialization. To integrate JsonStreamer while preserving rich metadata:&lt;/p&gt;

&lt;p&gt;They use PropertyMetadataLoader extension points to provide metadata to JsonStreamer. This lets JsonStreamer know property names, whether they're exposed, etc., without traversing the full object tree in memory.&lt;/p&gt;

&lt;p&gt;API Platform&lt;/p&gt;

&lt;p&gt;Use of ValueTransformers that can transform any value at runtime. But caution: heavy logic in transformers can degrade performance (they run per value).&lt;/p&gt;

&lt;p&gt;Symfony&lt;br&gt;
+1&lt;/p&gt;

&lt;p&gt;Use of ObjectMapper to convert entities (e.g., Doctrine objects) into POPOs (DTOs) that are suitable for streaming. This helps because entities often have lazy properties, proxies, relations etc., which complicate streaming.&lt;/p&gt;

&lt;p&gt;ESA (Edge Side APIs) pattern&lt;/p&gt;

&lt;p&gt;Edge Side APIs refers to breaking large JSON payloads into smaller, progressive calls or chunks, often delivered from the edge / CDN to improve perceived performance, especially in high latency/slow networks. In context of this talk:&lt;/p&gt;

&lt;p&gt;Instead of sending one huge JSON structure, partition or paginate so the client can start receiving some data quickly.&lt;/p&gt;

&lt;p&gt;Combine with streaming so that parts of the response start being delivered early (TTFB improves).&lt;/p&gt;

&lt;p&gt;Good user experience: user sees something quickly rather than waiting for full load.&lt;/p&gt;

&lt;p&gt;Takeaways&lt;/p&gt;

&lt;p&gt;Serializer works, but for large data sets it becomes inefficient.&lt;/p&gt;

&lt;p&gt;JsonStreamer gives significant improvements in both memory usage and time to first byte.&lt;/p&gt;

&lt;p&gt;When you have metadata layers (API Platform, JSON-LD), use the extension points provided to plug streaming without losing features.&lt;/p&gt;

&lt;p&gt;Avoid heavy computations / transformations in runtime‐hot paths (e.g., ValueTransformers).&lt;/p&gt;

&lt;p&gt;Design your API knowing these options early, because once core serialization path is deeply embedded, changing is hard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmarks &amp;amp; comparisons
&lt;/h3&gt;

&lt;p&gt;For a dataset of &lt;strong&gt;10,000 objects&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Memory usage / footprint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Serializer (traditional)&lt;/td&gt;
&lt;td&gt;~204 ms&lt;/td&gt;
&lt;td&gt;~16 MB (grows with size)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JsonStreamer&lt;/td&gt;
&lt;td&gt;~87 ms&lt;/td&gt;
&lt;td&gt;~8 MB (much more constant)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Challenges with metadata, JSON-LD and how API Platform adapts
&lt;/h3&gt;

&lt;p&gt;API Platform adds &lt;strong&gt;metadata&lt;/strong&gt;, JSON-LD contexts, and property metadata. That overhead makes serialization heavier. To integrate JsonStreamer while keeping these features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;PropertyMetadataLoader&lt;/strong&gt; extension points to provide metadata to JsonStreamer. This tells it which properties to expose, without traversing the full object tree.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;ValueTransformers&lt;/strong&gt; to adjust values at runtime. But beware: heavy logic here will degrade performance, since transformers run for every value.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;ObjectMapper&lt;/strong&gt; to convert entities (e.g., Doctrine objects) into POPOs (DTOs) that are easier to stream.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ESA (Edge Side APIs) pattern
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Edge Side APIs (ESA)&lt;/strong&gt; refers to breaking large JSON payloads into smaller, progressive chunks, often delivered from the edge or a CDN to improve perceived performance, especially in high-latency or slow networks.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of sending one huge JSON structure, partition or paginate so the client starts receiving data earlier.&lt;/li&gt;
&lt;li&gt;Combine with streaming so that parts of the response arrive incrementally, improving time-to-first-byte.&lt;/li&gt;
&lt;li&gt;The user experience is better: data appears quickly instead of waiting for everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Symfony’s Serializer is fine for small to medium datasets.&lt;/li&gt;
&lt;li&gt;JsonStreamer provides &lt;strong&gt;significant improvements&lt;/strong&gt; in memory usage and TTFB.&lt;/li&gt;
&lt;li&gt;API Platform integrates it through extension points (PropertyMetadataLoader, ValueTransformers, ObjectMapper).&lt;/li&gt;
&lt;li&gt;Avoid heavy runtime transformations for best performance.&lt;/li&gt;
&lt;li&gt;Design your API with these options in mind early—serialization decisions are very difficult to change later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Cover image by &lt;a href="https://ncls.tv/" rel="noopener noreferrer"&gt;Nicolas Detrez&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>techtalks</category>
      <category>api</category>
      <category>symfony</category>
    </item>
    <item>
      <title>SymfonyLive Paris 2025 : Ce qu’il faut retenir</title>
      <dc:creator>Thérage Kévin</dc:creator>
      <pubDate>Wed, 02 Apr 2025 11:53:54 +0000</pubDate>
      <link>https://dev.to/sensiolabs/symfonylive-paris-2025-ce-quil-faut-retenir-59ia</link>
      <guid>https://dev.to/sensiolabs/symfonylive-paris-2025-ce-quil-faut-retenir-59ia</guid>
      <description>&lt;p&gt;J’ai eu la chance d’assister au &lt;strong&gt;SymfonyLive Paris 2025&lt;/strong&gt;, et voici un résumé des annonces, conférences et tendances clés à retenir !&lt;/p&gt;

&lt;p&gt;Retrouvez les slides sur ce merveilleux dépôt (Merci Romain Gautier🙏) : &lt;a href="https://github.com/SymfonyLive/paris-2025-talks" rel="noopener noreferrer"&gt;https://github.com/SymfonyLive/paris-2025-talks&lt;/a&gt;)&lt;/p&gt;




&lt;h1&gt;
  
  
  Keynote : Scaling to 0  (🎤  Fabien Potencier)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  🚀 Qu'est-ce qu'un projet Open Source ?
&lt;/h2&gt;

&lt;p&gt;Un projet open source, ce n'est pas seulement du code. Voici la hiérarchie des priorités :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Communauté&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Une communauté active et une documentation solide sont essentielles pour assurer la pérennité d’un projet OS.&lt;/p&gt;

&lt;p&gt;Avec le temps, Symfony a intégré une nouvelle priorité : l’&lt;strong&gt;expérience développeur (DX)&lt;/strong&gt;. La hiérarchie évolue donc ainsi :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Communauté&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expérience développeur (DX)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🏗️ Comment créer une application Symfony simple ?
&lt;/h2&gt;

&lt;p&gt;Suite aux débats dans la communauté Laravel sur la complexité d’installation et la multitude de dépendances requises pour démarrer un projet, Fabien s’est posé la question :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Quelle est l'application Symfony la plus simple possible ?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En réponse, Symfony propose désormais trois nouveaux points d’entrée, permettant de commencer petit et d’évoluer progressivement.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔹 Symfony Hello
&lt;/h3&gt;

&lt;p&gt;Une version "Hello world" &lt;strong&gt;ultra-minimaliste&lt;/strong&gt; de Symfony, offrant la puissance du full-stack framework avec une empreinte réduite.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Échelle&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Taille d'équipe&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Structuration&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Conventions&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fichier unique&lt;/td&gt;
&lt;td&gt;Solo dev&lt;/td&gt;
&lt;td&gt;Aucune&lt;/td&gt;
&lt;td&gt;Aucune&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;✅ Idéal pour :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rapidité&lt;/li&gt;
&lt;li&gt;Flexibilité&lt;/li&gt;
&lt;li&gt;Prototypage (&lt;strong&gt;PoC&lt;/strong&gt;) minimaliste&lt;/li&gt;
&lt;li&gt;Projets personnels non maintenus&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔹 Symfony Solo
&lt;/h3&gt;

&lt;p&gt;Symfony &lt;strong&gt;sans Flex&lt;/strong&gt;, offrant un contrôle total sur l’architecture.&lt;/p&gt;

&lt;p&gt;💡 Il permet un front controller &lt;strong&gt;CLI &amp;amp; HTTP&lt;/strong&gt; grâce au composant &lt;code&gt;Runtime&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;PHP_SAPI&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'cli'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'console'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'http'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Échelle&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Taille d'équipe&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Structuration&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Conventions&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Petits/moyens projets&lt;/td&gt;
&lt;td&gt;Solo dev&lt;/td&gt;
&lt;td&gt;Aucune (personnalisable)&lt;/td&gt;
&lt;td&gt;Aucune (liberté totale)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;✅ Idéal pour :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commencer en toute légèreté&lt;/li&gt;
&lt;li&gt;Scalabilité progressive (&lt;strong&gt;scale as you go&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Applications mono-tâche&lt;/li&gt;
&lt;li&gt;Projets personnels avancés&lt;/li&gt;
&lt;li&gt;PoC élaborés&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔹 Symfony Team
&lt;/h3&gt;

&lt;p&gt;Le framework &lt;strong&gt;full-stack classique&lt;/strong&gt;, conçu pour les projets de grande envergure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Échelle&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Taille d'équipe&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Structuration&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Conventions&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Moyens à grands projets&lt;/td&gt;
&lt;td&gt;Équipe de devs&lt;/td&gt;
&lt;td&gt;Standardisée&lt;/td&gt;
&lt;td&gt;Documentées&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;✅ Idéal pour :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Industrialisation&lt;/li&gt;
&lt;li&gt;Travail collaboratif&lt;/li&gt;
&lt;li&gt;Maintenance à long terme&lt;/li&gt;
&lt;li&gt;Projets professionnels&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚙️ Un petit teaser à propos de Symfony 8
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disparition des fichiers XML&lt;/strong&gt; pour la configuration des routes, désormais remplacés par des fichiers PHP.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤖 Hot Takes sur les LLMs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Les LLMs ne remplaceront pas les développeurs.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ce sont d’excellents &lt;em&gt;stagiaires&lt;/em&gt; : disponibles 24/7, polis… mais parfois incohérents !&lt;/li&gt;
&lt;li&gt;Très utiles contre le &lt;strong&gt;syndrome de la page blanche&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; s’avère particulièrement efficace pour la génération de code et de documentation.&lt;/li&gt;
&lt;li&gt;Possibilité d’utiliser les LLMs pour :

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mise à jour automatique du code&lt;/strong&gt; (avec prudence).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation des PRs&lt;/strong&gt; pour améliorer la revue de code.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h1&gt;
  
  
  Symfony ObjectMapper Component (🎤 Antoine Bluchet)
&lt;/h1&gt;

&lt;p&gt;Après une partie d'historique sur l'arrivée du composant dans Symfony. Antoine a démarré par présenter le ce qui existait dans les autres langages et en PHP.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Object Mapping dans d'autres languages
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Spring : jackson&lt;/li&gt;
&lt;li&gt;.Net : automapper&lt;/li&gt;
&lt;li&gt;Java : 

&lt;ul&gt;
&lt;li&gt;modelmapper&lt;/li&gt;
&lt;li&gt;Java Map Struct&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Ruby : Ruby Object Mapper (ROM)&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  🛠️ Etat du mapping en d'objet en PHP
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Valinor&lt;/strong&gt; : offre du mapping et bien plus (ce qui n'est pas une bonne chose selon Antoine)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automapper (JoliCode)&lt;/strong&gt; : Fonctionne sur le principe de génération de classe PHP pour mapper les objets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MicroMapper (SymfonyCasts)&lt;/strong&gt; : version simpliste.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;et bien d'autres&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎯 Mapping vs Hydratation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hydratation&lt;/strong&gt; : remplir un objet avec des données.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mapping&lt;/strong&gt; : transformer un objet en un autre objet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Contrairement au serializer, le mapping en PHP permet une meilleure séparation des responsabilités et une meilleure intégration avec Symfony.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le composant
&lt;/h2&gt;

&lt;p&gt;Le composant offre une approche simple via des attributs permettant de faire un mapping rapide via des attributs.&lt;/p&gt;

&lt;p&gt;vous pouvez retrouver la Pull-Request &lt;a href="https://github.com/symfony/symfony/pull/51741" rel="noopener noreferrer"&gt;ici&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exemple :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\ObjectMapper\ObjectMapper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\ObjectMapper\Attributes\Map&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// This maps class `A` to class `B`.&lt;/span&gt;
&lt;span class="na"&gt;#[Map(B::class)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This maps A::foo to B::bar.&lt;/span&gt;
    &lt;span class="na"&gt;#[Map(target: 'bar')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// This calls ucfirst on A::transform&lt;/span&gt;
    &lt;span class="na"&gt;#[Map(transform: 'ucfirst')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// This doesn't map A::bar if it's value is falsy.&lt;/span&gt;
    &lt;span class="na"&gt;#[Map(if: 'boolval')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$mapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$mapper&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  PostgreSQL pour vos besoins NoSQL (🎤 David Buchmann)
&lt;/h1&gt;

&lt;p&gt;Après une brève introduction et un rappel sur le fait que le NoSQL n'est pas clairement défini alternant entre base de donnée non-relationnel et le fait qu'une base de donnée relationnel permette aussi de stocker des élements sans relation, il a fait le tours des fonctionnalité de PostGreSQL et de MySQL (ce qui a été une petite surprise compte tenu du titre) permettant de stocker et de requête du JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 Type de colonne JSON vs JSONb
&lt;/h2&gt;

&lt;p&gt;En PostGreSQL deux types de colonnes son proposés &lt;em&gt;JSON&lt;/em&gt; et &lt;em&gt;JSONb&lt;/em&gt; avec leurs avantages et leurs incovénients.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Avantages&lt;/th&gt;
&lt;th&gt;Inconvénients&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JSON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Préserve l'ordre des clés dans l'objet&lt;/td&gt;
&lt;td&gt;stocké sous forme de chaîne de caractères&lt;br&gt;aucune optimisation des caractères répétés&lt;br&gt;accepte du JSON invalide (garde les clés dupliquées)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JSONb&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;format binaire optimisé&lt;br&gt;Performant pour les requêtes&lt;/td&gt;
&lt;td&gt;Aucune mention faite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;En dehors de cas d'utilisation non standard, il est préférable d'utiliser JSONB.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Opérateurs utiles
&lt;/h2&gt;

&lt;p&gt;Soit le JSON suivant :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"David"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-&amp;gt;&lt;/code&gt; : extrait une information JSON. ie : renvoie &lt;code&gt;"David"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; : extrait une donnée native tout en etant 2.5 fois plus rapide. ie : renvoie &lt;code&gt;David&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L'opérateur &lt;code&gt;-&amp;gt;&lt;/code&gt; existe aussi pour MySQL mais il faut préciser le JSONPath :&lt;br&gt;
&lt;code&gt;-&amp;gt;'$.author'&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Ajouter un index sur un champ json
&lt;/h2&gt;

&lt;p&gt;Il est possible d'ajouter un index sur un champ JSON via l'instruction&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;json_column&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;json_column&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce qui améliore le temps de requête considérablement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Générer une colonne à partir d'une propriété JSON
&lt;/h2&gt;

&lt;p&gt;Il est possible d'extraire et de générer une colonne à partir d'une propriété JSON via l'instruction&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="n"&gt;VAR_CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_column&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;l'extraction d'une date est compliqué est nécessite de passer par une procédure stockée.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fonctions utiles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;contient :

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;json_column @&amp;gt; '{"author": "David"}'&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;la clé existe :

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;json_column ? 'author'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jsonb_exists(json_column, 'author')&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;au moins une clé existe :

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;json_column ?| array['author', 'it_does_not_exists']&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jsonb_exists_any(json_column, array['author', 'it_does_not_exists'])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;toutes les clé existent :

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;json_column ?&amp;amp; array['author', 'it_does_not_exists']&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jsonb_exists_all(json_column, array['author', 'it_does_not_exists'])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h1&gt;
  
  
  Passkeys pour une authentification fluide et sécurisée (🎤 Rémi Janot)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  🔐 Problèmes des mots de passe
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Créés dans les années 60, ils sont devenus vulnérables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brute-force&lt;/strong&gt;, &lt;strong&gt;phishing&lt;/strong&gt;, &lt;strong&gt;réutilisation&lt;/strong&gt;…&lt;/li&gt;
&lt;li&gt;HaveIBeenPwned révèle l’ampleur des fuites de données.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔑 2FA / MFA
&lt;/h2&gt;

&lt;p&gt;Réside sur 3 piliers :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ce que l'on sait&lt;/strong&gt; (mot de passe).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ce que l'on a&lt;/strong&gt; (téléphone, clé de sécurité).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ce que l'on est&lt;/strong&gt; (empreinte digitale, reconnaissance faciale).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🌍 WebAuthn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;API JS, supportée par &lt;strong&gt;96 % des navigateurs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;S'appuie sur les 3 piliers du 2FA / MFA&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  💻 Démonstration
&lt;/h1&gt;

&lt;p&gt;La démonstration d'un intégration d'un système d'authentification WebAuthn a ensuite été présentée par Rémi avec une explication étape par étape de ce qui était transmis au serveur (payload JSON, request/response).&lt;/p&gt;

&lt;p&gt;Retrouvez le dépôt du projet de démonstration &lt;a href="https://github.com/rjanot/webauthn-demo-symfony" rel="noopener noreferrer"&gt;ici&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Symfony UX : Points forts de 2024 et perspectives d’avenir (🎤 Simon André)
&lt;/h1&gt;

&lt;p&gt;5 ans après : où en est Symfony UX ?&lt;/p&gt;

&lt;h2&gt;
  
  
  👨‍💻 Symfony UX core team
&lt;/h2&gt;

&lt;p&gt;Officialisation de la core team Symfony UX avec pour membres :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ryan Weaver&lt;/li&gt;
&lt;li&gt;Kévin Bond&lt;/li&gt;
&lt;li&gt;Hugo Alliaume&lt;/li&gt;
&lt;li&gt;Simon André&lt;/li&gt;
&lt;li&gt;Mathéo Daninos&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📌 Adoption
&lt;/h2&gt;

&lt;p&gt;Utilisé par &lt;strong&gt;Symfony&lt;/strong&gt;, &lt;strong&gt;EasyAdmin&lt;/strong&gt;, &lt;strong&gt;PrestaShop&lt;/strong&gt;, &lt;strong&gt;Sylius&lt;/strong&gt;, &lt;strong&gt;SensioLabs&lt;/strong&gt;, et même le &lt;strong&gt;ministère de l’Intérieur&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 Quelques chiffres
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2022&lt;/strong&gt; → 800 000 téléchargements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2023&lt;/strong&gt; → 4 000 000 téléchargements
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2024&lt;/strong&gt; → 1 500 000 téléchargements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les packages UX les plus populaires :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;95 000/semaine&lt;/strong&gt; : Twig Component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50 000/semaine&lt;/strong&gt; : UX Icons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;40 000/semaine&lt;/strong&gt; : Live Component.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔮 UX en 2025
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typony Express&lt;/strong&gt; : Création d'un jeu &lt;a href="https://zty.pe/" rel="noopener noreferrer"&gt;https://zty.pe/&lt;/a&gt; entièrement avec Symfony UX comme une expérimentation et avec une documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refonte du site UX&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un UX Bundle unique&lt;/strong&gt; pour simplifier l’écosystème et l'ajout de nouveau "modules" UX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UX v3 prévu pour l’été 2025&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symfony 8 : retour à une synchronisation des versions&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Rôles &amp;amp; Permissions : Marque blanche et Feature Flipping  (🎤 Florian Bogey)
&lt;/h1&gt;

&lt;p&gt;Florian nous as montré comment Rôle et Permissions peuvent être utilisé de concert pour permettre la mise en place d'un marque blanche et d'un mécanisme de feature flag/flipping/toggle/whatever you may call something that activate or not a feature on your website.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎭 Rôles vs Permissions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rôle&lt;/strong&gt; : catégorie utilisateur statique.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission&lt;/strong&gt; : règle métier dynamique.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Symfony gère les permission via les &lt;strong&gt;Voters&lt;/strong&gt;.&lt;br&gt;
Il est possible de cumuler les attributs &lt;code&gt;#[IsGranted()]&lt;/code&gt; pour différencier contrôles d'accès par les rôles des contrôles d'accès par permissions.&lt;/p&gt;

&lt;p&gt;Exemple :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Security\Http\Attribute\IsGranted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FooController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('/foo/{id}', name: 'foo')]&lt;/span&gt;
    &lt;span class="na"&gt;#[IsGranted('ROLE_ADMIN')]&lt;/span&gt;
    &lt;span class="na"&gt;#[IsGranted('foo_voter', 'id')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🗳 Voter
&lt;/h2&gt;

&lt;p&gt;Les voters peuvent être configurés selon 4 stratégies :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Affirmative (par défaut) : Au moins un voter autorise l'action.&lt;/li&gt;
&lt;li&gt;Consensus : nécessite de voter qui autorise l'action que de voter qui ne l'interdise.&lt;/li&gt;
&lt;li&gt;Unanime : autorise l'action si aucun voter ne l'a interdite.&lt;/li&gt;
&lt;li&gt;Priority : renvoie la décision du premier voter qui ne s'est pas abstenu en s'appuyant sur la priorité définie dans les services.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🏢 Gestion de clients exigeants
&lt;/h2&gt;

&lt;p&gt;Deux besoins principaux :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Marque blanche&lt;/strong&gt; → Identité visuelle, branding personnalisé.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature Flipping&lt;/strong&gt; → Activer/désactiver des fonctionnalités par client.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  💮 Marque blanche
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Définition :&lt;/strong&gt; La marque blanche est un modèle commercial qui permet à une entreprise de vendre des produits ou des services conçus par une autre entreprise sous sa propre marque.&lt;/p&gt;

&lt;p&gt;Afin de pouvoir avoir une application "Multi-tenant" (comprendre un seul code source pour plusieurs marques/clients différents), Florian nous as expliqué qu'il était nécessaire que chaque dispose de sont propre fichier d'environnement.&lt;/p&gt;

&lt;p&gt;C'est rendu possible grâce&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;l'ajout d'une variable d'environnement côté serveur web (SetEnv apache et set nginx)&lt;/li&gt;
&lt;li&gt;une légère modification du front controller (le fichier public/index.php) de Symfony afin d'ajouter la logique permettant le chargement du bon fichier DotEnv.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DotEnv&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%s/.env.%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'APP_SOME_NAME'&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;une extension TWIG pour transmettre les variables d'environnement nécessaire a TWIG.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🔄 Feature Flipping
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Définition&lt;/strong&gt; : Permettre d'activer/désactiver une fonctionnalité ou des partie d'une application sous certaines conditions.&lt;/p&gt;

&lt;p&gt;Après une présentation de plusieurs alternative et bundle permettant un feature flipping, Florian a présenté la solution maison qu'ils ont mis en place chez GL Events (retrouver les slides de Florian &lt;a href="https://slides.com/florianbogey/sf-live-roles-permissions" rel="noopener noreferrer"&gt;ici&lt;/a&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Async avec Messenger, AMQP et Mercure
&lt;/h1&gt;

&lt;h2&gt;
  
  
  🔄 Rappel sur Messenger
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Permet d’exécuter des tâches &lt;strong&gt;asynchrones&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Transports disponibles : &lt;strong&gt;sync&lt;/strong&gt;, &lt;strong&gt;in-memory&lt;/strong&gt;, &lt;strong&gt;AMQP&lt;/strong&gt;…&lt;/li&gt;
&lt;li&gt;Messages stockés dans une &lt;strong&gt;queue&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Gestion avec un &lt;strong&gt;superviseur&lt;/strong&gt; (&lt;code&gt;systemd&lt;/code&gt;, &lt;code&gt;supervisord&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Conseil&lt;/strong&gt; : Pour les imports &lt;strong&gt;async&lt;/strong&gt;, ne stockez &lt;strong&gt;pas&lt;/strong&gt; les fichiers en BDD/queue, mais sur un disque partagé.&lt;/p&gt;

&lt;h2&gt;
  
  
  📡 Donner du feedback en temps réel (exemple : import)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Mauvaises solutions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XHR Polling&lt;/strong&gt; : inefficace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSockets&lt;/strong&gt; : complexe et lourd à maintenir.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Solution moderne : Mercure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Surcouche à SSE (Server-Sent Events)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full duplex&lt;/strong&gt; et optimisé pour les updates en temps réel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installation&lt;/strong&gt; via Composer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connexion JS&lt;/strong&gt; via &lt;code&gt;new EventSource()&lt;/code&gt; présent nativement dans les navigateurs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔐 Sécurité
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Authentification JWT intégrée.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publisher authentifié&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscriber anonyme ou non&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Symfony UX + Turbo + SSE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;turbo_stream_listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'csv:'&lt;/span&gt; &lt;span class="err"&gt;~&lt;/span&gt; &lt;span class="nv"&gt;importId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permet de recevoir des mises à jour &lt;strong&gt;sans écrire une ligne de JS&lt;/strong&gt; !&lt;/p&gt;




&lt;h1&gt;
  
  
  Atteindre la qualité d’une SPA avec HTMX et Twig
&lt;/h1&gt;

&lt;h2&gt;
  
  
  🌍 HTMX : une alternative aux SPA
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Créé par Carson Gross en &lt;strong&gt;2020&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;50 KB&lt;/strong&gt;, plus léger que jQuery.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠️ Fonctions principales
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-get / hx-post&lt;/code&gt; : appels Ajax.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-trigger&lt;/code&gt; : changement d’événement (&lt;code&gt;once&lt;/code&gt;, &lt;code&gt;throttle&lt;/code&gt;, &lt;code&gt;revealed&lt;/code&gt;…).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-target&lt;/code&gt; : où afficher la réponse Ajax.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-swap&lt;/code&gt; : méthode d’insertion (&lt;code&gt;innerHTML&lt;/code&gt;, &lt;code&gt;outerHTML&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;…).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-boost&lt;/code&gt; : transforme tous les liens en requêtes AJAX.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📌 Twig et animations CSS
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Utilisation de &lt;code&gt;renderBlock()&lt;/code&gt; pour ne rendre qu’un bloc spécifique d’un template.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Morphing des éléments via un ID conservé.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Du Lego de composants pour un bundle Gotenberg
&lt;/h1&gt;

&lt;h2&gt;
  
  
  📜 Génération de PDF en Symfony
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Utilisation du bundle &lt;strong&gt;Gotenberg&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Permet de &lt;strong&gt;convertir HTML en PDF facilement&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Exemples et démonstrations disponibles sur &lt;a href="https://medium.com/the-sensiolabs-tech-blog/how-to-generate-a-pdf-file-in-a-few-lines-of-code-with-symfony-39786a679d29" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>symfony</category>
      <category>symfonylive</category>
      <category>discuss</category>
      <category>php</category>
    </item>
    <item>
      <title>Symfony &amp; Doctrine Migrations: Validation in CI</title>
      <dc:creator>Thérage Kévin</dc:creator>
      <pubDate>Thu, 05 Sep 2024 10:13:59 +0000</pubDate>
      <link>https://dev.to/sensiolabs/symfony-doctrine-migrations-validation-in-ci-54pd</link>
      <guid>https://dev.to/sensiolabs/symfony-doctrine-migrations-validation-in-ci-54pd</guid>
      <description>&lt;p&gt;I had the opportunity to work on a project with a team that was relatively new to Doctrine migrations. To help them get used to it, and to discard the possibility of having pull (or merge) requests with changes to doctrine entities without generating a migration.&lt;/p&gt;

&lt;p&gt;Here is how I did it. I hope you'll enjoy it!&lt;/p&gt;

&lt;h2&gt;
  
  
  How Doctrine Migrations works
&lt;/h2&gt;

&lt;p&gt;When generating the migration, Doctrine will make a delta between its mapping and the current schema of the database. With this delta in "mind" (dare I say 😉) it will generate a &lt;strong&gt;migration file&lt;/strong&gt; with two main methods :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;up&lt;/code&gt; applies the SQL commands to fill the gap between the current database schema and its mapping. Used to deploy changes in the schema of your database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;down&lt;/code&gt; allows to revert the migration with the SQL commands needed to "negate" the changes made in the up method. Used to roll back changes in the schema of your database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The magic trick
&lt;/h2&gt;

&lt;p&gt;There is currently no way to easily check if a migration has not been generated. Having this code merged could lead to a database schema being out of sync with your entity mapping and so resulting in a server error.&lt;/p&gt;

&lt;p&gt;The keywords in the above description are &lt;strong&gt;migration files&lt;/strong&gt;. I'll use the fact that, running the command bin/console doctrine:migration:diff will result in a newly generated file and will fail if there are no changes to apply.&lt;/p&gt;

&lt;p&gt;Knowing the list of existing files before the execution of that command, and then running it, can let me know that there are changes that were not committed to a &lt;strong&gt;migration file&lt;/strong&gt; in this pull (or merge) request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create your database&lt;/li&gt;
&lt;li&gt;Run your existing migrations&lt;/li&gt;
&lt;li&gt;Then run the step to check for missing changes (see below)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Advantages
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Testing that your migrations does not fail&lt;/li&gt;
&lt;li&gt;Ensure database schema consistency with Doctrine's mapping&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  You want the code snippet right!?
&lt;/h2&gt;

&lt;p&gt;Here is the bash code :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail

&lt;span class="c"&gt;# run doctrine migration diff to check if there is a new migration file generated and check last exit code&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;bin/console doctrine:migrations:diff &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error ! bin/console doctrine:migration:diff found a new migration which must not be the case."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c"&gt;# cat last file (should be the newly generated one)&lt;/span&gt;
    &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-Art&lt;/span&gt; migrations/&lt;span class="k"&gt;*&lt;/span&gt;.php | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c"&gt;# remove that file (just in case to comply with my paranoïac side)&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-Art&lt;/span&gt; migrations/&lt;span class="k"&gt;*&lt;/span&gt;.php | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! You can now ensure that each pull (or merge) request has working migrations, with no pending changes left out of the migrations!&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok but why not use &lt;code&gt;bin/console doctrine:schema:validate&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;The reason was that the project we were working on was using doctrine's schema_filter configuration to filter out some tables we did not want to deal with (project-related inconvenience).&lt;/p&gt;

&lt;p&gt;The problem with bin/console doctrine:schema:validate was that it did not take care of the configuration, and so was dumping changes (trying to delete all the "normally" filtered out tables) not related to what we wanted.&lt;/p&gt;

&lt;p&gt;A colleague told me that this is a known issue that might be fixed soon (&lt;a href="https://github.com/doctrine/migrations/issues/1406" rel="noopener noreferrer"&gt;https://github.com/doctrine/migrations/issues/1406&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Thank you for reading this article and please leave your comments if you have any questions!&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Cover image: Unsplash by Ethan Weil&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>doctrine</category>
      <category>ci</category>
      <category>doctrinemigration</category>
    </item>
    <item>
      <title>How to transform Component Development with Storybook and Symfony UX ?</title>
      <dc:creator>Matheo Daninos</dc:creator>
      <pubDate>Mon, 17 Jun 2024 14:17:39 +0000</pubDate>
      <link>https://dev.to/sensiolabs/how-to-transform-component-development-with-storybook-and-symfony-ux--c86</link>
      <guid>https://dev.to/sensiolabs/how-to-transform-component-development-with-storybook-and-symfony-ux--c86</guid>
      <description>&lt;p&gt;Hey everyone! I am so excited about this article because what I'm going to show you here has been my dream since I first heard about Symfony UX! I will demonstrate a setup that makes me incredibly productive, but most importantly, brings me a lot of joy.&lt;/p&gt;

&lt;p&gt;As you know, I love working with components (TwigComponent/LiveComponent), and I also adore Storybook! In my last article, I showed you how to use Storybook to share your components with your team. Today, we're going to dive even deeper. Storybook has been a game changer for me primarily because it provides the best environment to work with components. Components are visual and interactive, and Storybook offers a fantastic playground to view, interact with, and test your components. It helps you create beautiful, interactive, fun, and robust components. So, let’s see how this works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Working in isolation
&lt;/h2&gt;

&lt;p&gt;If you remember from my first article, we discussed the four main rules of component architecture. One of these rules is independence. Your components should not depend on the context of the page; you should be able to move your component from one page to another without any issues.&lt;/p&gt;

&lt;p&gt;The great thing about using Storybook is that it enforces this rule. Storybook operates by isolating each component, allowing you to test them one by one in complete isolation. This ensures that if your component works in Storybook, it will work seamlessly on all your pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot Reload
&lt;/h3&gt;

&lt;p&gt;So when I am working on a new component, the first thing I do is create a story. A really basic one that just gives me the environment to start building.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Alert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../templates/components/Alert.html.twig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sensiolabs/storybook-symfony-webpack5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;twig:Alert&amp;gt;
        {{ message }}
      &amp;lt;/twig:Alert&amp;gt;
  `&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then I can just focus on creating my component. With hot reload, I get quick feedback, which makes things really comfortable for a visual component like an alert.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0xdg6chddff7wdaaa8l.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0xdg6chddff7wdaaa8l.gif" alt="Show storybook hot reload"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No need to press F5 anymore or try your component on random pages. Just write a story for your Storybook, and you will have a nice development environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactions
&lt;/h3&gt;

&lt;p&gt;When working on a new feature, one of the most frustrating aspects can be having to interact with your component to access the part you want to test. For example, I have a small form here, and I want to see how it looks when the entered email is invalid.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdsfybf9hb9smv11jhcq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdsfybf9hb9smv11jhcq.gif" alt="Form in storybook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Doing this manually can be a huge waste of time, especially for larger components. That's why I don’t do that anymore! With Storybook, you can automate your interactions to reach the exact state you want to test&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I do that ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, I set up my Default story:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Email&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../templates/components/Email.html.twig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sensiolabs/storybook-symfony-webpack5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="s2"&gt;`
        &amp;lt;twig:Email/&amp;gt;
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, I add a new story called ‘WrongEmail’:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Email&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../templates/components/Email.html.twig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sensiolabs/storybook-symfony-webpack5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@storybook/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WrongEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this story, I define a play function. This function contains snippets of code executed after the story renders, allowing you to interact with your components and test scenarios that would otherwise require manual intervention.&lt;/p&gt;

&lt;p&gt;The play function looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@storybook/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nl"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wrongemail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FirstName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Kobe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LastName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bryant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Storybook provides a wrapper around &lt;a href="https://testing-library.com/" rel="noopener noreferrer"&gt;https://testing-library.com/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are not familiar with this library, it is widely used in the JS community, very robust, and has a strong community around it, making it easy to find good resources.&lt;/p&gt;

&lt;p&gt;So, if we get back to our play function, we see that we define an argument &lt;strong&gt;canvasElement&lt;/strong&gt;. This &lt;strong&gt;canvasElement&lt;/strong&gt; represents the canvas where our story is rendered.&lt;/p&gt;

&lt;p&gt;Then we do the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, we wrap our canvas in an object to enable better assertions later.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wrongemail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This line simulates the user typing ‘wrongemail’ in the input with the label Email.&lt;/p&gt;

&lt;p&gt;We do the same thing for the first name and last name:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FirstName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Kobe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LastName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bryant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we click on the submit button:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And just like this, Storybook will perform the interactions for me, so I no longer need to do all these steps by hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgsqr3n2zra72aodt60i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgsqr3n2zra72aodt60i.gif" alt="interactions storybook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also easily debug what happens using the interaction panel and go back to previous steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4j05b8g8u9o6w8djcf8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4j05b8g8u9o6w8djcf8.gif" alt="step by step in storybook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just love this feature—it saves me so much time. Components are visual and interactive, and having an environment that lets me see and interact with my components easily is a real game changer.&lt;/p&gt;

&lt;p&gt;And you know what? We can do even more! We can use Storybook to test our components!&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;I have a component, &lt;code&gt;RadioList&lt;/code&gt;, that displays a list of radios and a search bar. When the user types into the search bar, the component updates the list of radios accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgxj5nxdylnu12jo8l1t.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgxj5nxdylnu12jo8l1t.gif" alt="radio component in storybook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I know the content of my database, so I want to ensure that when I type "90," four radios are displayed.&lt;/p&gt;

&lt;p&gt;We already know how to do this by writing a play function and adding an assertion at the end.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@sensiolabs/storybook-symfony-webpack5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@storybook/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;twig:RadioList /&amp;gt;
    `&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Play&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;searchbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;90&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryAllByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And just like that, I have a real test that fully tests my LiveComponent from PHP to JavaScript!&lt;/p&gt;

&lt;p&gt;I can see that my test is working by checking the interaction panel. I now have a green "Pass" indicator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpn5aci42d4z0o8ywt242.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpn5aci42d4z0o8ywt242.png" alt="Tests passes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can go even further and run all our tests at once by running:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm run test-storybook


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What's interesting is that even simple stories with no interaction are tests. Storybook checks that all your components are rendered correctly. This is just a test runner, so you can completely run all your tests in your CI.&lt;/p&gt;

&lt;p&gt;By leveraging Storybook for testing, you ensure a seamless and efficient development process, catching issues early and maintaining high-quality components.&lt;/p&gt;

&lt;p&gt;Leveraging Storybook has transformed the way I develop and test components. From working in isolation to hot reloading, automating interactions, and writing comprehensive tests, Storybook provides a development environment that boosts productivity and fun. Although we still rely on Node.js, it’s not a big deal since Storybook is only used in your development environment and not deployed to production.&lt;/p&gt;

&lt;p&gt;Components are often visual elements of your application, and maintaining a visual development approach greatly enhances the process. Storybook ensures that your components are robust, interactive, and beautifully designed. I hope you enjoyed this article and found it helpful. See you soon!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to use the new Symfony Maker command to work with GitHub Webhooks</title>
      <dc:creator>Maelan Le Borgne</dc:creator>
      <pubDate>Fri, 31 May 2024 07:14:51 +0000</pubDate>
      <link>https://dev.to/sensiolabs/how-to-use-the-new-symfony-maker-command-to-work-with-github-webhooks-2c8n</link>
      <guid>https://dev.to/sensiolabs/how-to-use-the-new-symfony-maker-command-to-work-with-github-webhooks-2c8n</guid>
      <description>&lt;p&gt;Recently I've been working on a tool that would gather some open-source contribution metrics from our teams. We mostly focus on contributions on GitHub, so I started studying the &lt;a href="https://docs.github.com/en/rest" rel="noopener noreferrer"&gt;API&lt;/a&gt; to see how I could get the relevant data I needed to process. If I wanted to get fresh data on a regular basis, I would have sent requests periodically. But polling API endpoints is &lt;a href="https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api#avoid-polling" rel="noopener noreferrer"&gt;not ideal&lt;/a&gt;, especially when the services you are accessing can &lt;strong&gt;come to you instead&lt;/strong&gt; !&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Webhooks !
&lt;/h2&gt;

&lt;p&gt;Webhooks are a pretty common way for services from the outside world to communicate with your own application. It is quite similar to the event subscriber in its design :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A remote service declares a list of &lt;strong&gt;steps in its lifecycle&lt;/strong&gt; (for github: an issue has been opened, a comment has been made on a PR, ...), and for each of theses steps it will &lt;strong&gt;dispatch&lt;/strong&gt; an event containing relevant data.&lt;/li&gt;
&lt;li&gt;You can &lt;strong&gt;subscribe&lt;/strong&gt; to any of these events, and you'll get notified when they are dispatched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main difference with your local event based system resides in the &lt;strong&gt;transport&lt;/strong&gt; : events are sent over the network to a custom endpoint where you implement your own logic to handle the events.&lt;/p&gt;

&lt;p&gt;Nowadays, Webhooks are widely used for a lot of different purposes (getting information on a mail delivery, get notified of the steps of a payment process, ...), and the process to create a webhook is somehow always the same :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expose an endpoint.&lt;/li&gt;
&lt;li&gt;Check if the request should be processed.&lt;/li&gt;
&lt;li&gt;Check if the request if authorized and well formed.&lt;/li&gt;
&lt;li&gt;Process the request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make things easier for developers, Symfony released the &lt;a href="https://symfony.com/blog/new-in-symfony-6-3-webhook-and-remoteevent-components" rel="noopener noreferrer"&gt;Webhook and RemoteEvent&lt;/a&gt; components  in &lt;strong&gt;&lt;a href="https://symfony.com/blog/category/living-on-the-edge/6.3" rel="noopener noreferrer"&gt;Symfony 6.3&lt;/a&gt;&lt;/strong&gt;. The &lt;strong&gt;Webhook&lt;/strong&gt; component focuses on making the creation of endpoint and validation of request easy, while &lt;strong&gt;RemoteEvent&lt;/strong&gt; is about making the event's payload transit on Messenger and be handled by a RemoteEventConsumer, where your logic will live.&lt;/p&gt;

&lt;p&gt;To install these components, run :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;composer require symfony/webhook


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  How does this work ?
&lt;/h2&gt;

&lt;p&gt;Okay, now we've installed the component, where should we start ?&lt;/p&gt;

&lt;p&gt;First of all, let's set up our webhook so that we can effectively handle the requests that will be sent to us by GitHub.&lt;/p&gt;

&lt;p&gt;At the time of writing, the component's documentation isn't fully released yet, so it may be a little bit confusing at first. But don't sweat : to make your life easier, a new Maker command was introduced !&lt;/p&gt;

&lt;p&gt;To create a new Webhook, run :&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;symfony console make:webhook


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The maker will ask you for the webhook name. It will be used to generate the webhook url (&lt;a href="https://example.com/webhook/the_name_goes_here" rel="noopener noreferrer"&gt;https://example.com/webhook/the_name_goes_here&lt;/a&gt;). Let’s call it “&lt;strong&gt;github&lt;/strong&gt;”.&lt;br&gt;&lt;br&gt;
Next you'll be asked you for the RequestMatchers to use. For GitHub, we know that the events are sent via POST requests and the format is JSON, so we'll add &lt;code&gt;MethodRequestMatcher&lt;/code&gt; and &lt;code&gt;IsJsonRequestMatcher&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb45mo6xrah7t3kfdvqe3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb45mo6xrah7t3kfdvqe3.gif" alt="Screenshot of the command output indicating the success and creation of 3 new files"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can see that the command added some config to &lt;code&gt;config/packages/webhook.yaml&lt;/code&gt; and created two files in our project source dir : &lt;code&gt;src/Webhook/GithubRequestParser.php&lt;/code&gt; and &lt;code&gt;src/RemoteEvent/GithubWebhookConsumer&lt;/code&gt;.&lt;br&gt;
Hooray 🎉 ! Now we have some basis to work on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tweaking the code
&lt;/h2&gt;

&lt;p&gt;Let's dive into the generated class &lt;code&gt;src/Webhook/GithubRequestParser.php&lt;/code&gt; and see what changes we have to make to fit our needs.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// src/Webhook/GithubRequestParser.php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Webhook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\ChainRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Exception\JsonException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\RequestMatcherInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\RemoteEvent\RemoteEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Webhook\Client\AbstractRequestParser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Webhook\Exception\RejectWebhookException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GithubRequestParser&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractRequestParser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;RequestMatcherInterface&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChainRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IsJsonRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MethodRequestMatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @throws JsonException
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;doParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;\SensitiveParameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;RemoteEvent&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO: Adapt or replace the content of this method to fit your need.&lt;/span&gt;

        &lt;span class="c1"&gt;// Validate the request against $secret.&lt;/span&gt;
        &lt;span class="nv"&gt;$authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-Authentication-Token'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$authToken&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RejectWebhookException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Invalid authentication token.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Validate the request payload.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RejectWebhookException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Request payload does not contain required fields.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Parse the request payload and return a RemoteEvent object.&lt;/span&gt;
        &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RemoteEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can see that the RequestMatchers previously selected were added to a &lt;code&gt;ChainRequestMatcher&lt;/code&gt;. We don't have anything else to do in this method 🥳.  &lt;/p&gt;

&lt;p&gt;Now in the &lt;code&gt;doParse&lt;/code&gt; method, we see that three main steps are hinted by the comments : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validating&lt;/strong&gt; the request against the secret.&lt;/li&gt;
&lt;li&gt;Check the request is &lt;strong&gt;well formed&lt;/strong&gt; (mandatory fields are present, the expected format is respected ...).&lt;/li&gt;
&lt;li&gt;Returning a &lt;strong&gt;remote event&lt;/strong&gt; holding the payload.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub has an &lt;a href="https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries" rel="noopener noreferrer"&gt;interesting documentation&lt;/a&gt; on request validation, with some snippets in Ruby, JavaScript, Python ... but no PHP 😢. No worries, I made the translation for you :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-Hub-Signature-256'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;is_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;str_starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sha256='&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256='&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContent&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RejectWebhookException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Invalid authentication token.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, for the validation : we're expecting a variety of events to knock at our webhook's door, so we won't be too strict on format validation.&lt;br&gt;&lt;br&gt;
To create a &lt;code&gt;RemoteEvent&lt;/code&gt; we'll need a &lt;strong&gt;name&lt;/strong&gt; (action) and an &lt;strong&gt;id&lt;/strong&gt;. That will be our minimum requirements :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'number'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RejectWebhookException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Request payload does not contain required fields.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then all that's left to do is to create and return a &lt;code&gt;RemoteEvent&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RemoteEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
    &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
    &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'number'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
    &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This event will be passed over to Messenger, that in turn will pass it to your &lt;code&gt;GithubWebhookEventConsumer&lt;/code&gt; (thanks to the &lt;code&gt;#[AsRemoteEventConsumer('github')]&lt;/code&gt; attribute on the class).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// src/RemoteEvent/GithubWebhookConsumer.php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\RemoteEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\RemoteEvent\Consumer\ConsumerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\RemoteEvent\RemoteEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[AsRemoteEventConsumer('github')]&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GithubWebhookConsumer&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ConsumerInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RemoteEvent&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Implement your own logic here&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is where you'll put your &lt;strong&gt;custom logic&lt;/strong&gt; : mapping to DTO, persisting to database, ... whatever fits your needs.&lt;/p&gt;

&lt;p&gt;Finally, head to &lt;code&gt;config/packages/webhook.yaml&lt;/code&gt; and set up a secret (a random string of text with high entropy). This being sensitive data, it should be referenced here but stored in an environment variable (&lt;a href="https://symfony.com/doc/current/configuration/secrets.html" rel="noopener noreferrer"&gt;see Symfony documentation&lt;/a&gt;).&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;# config/packages/webhook.yaml&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;webhook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;routing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
      &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App\Webhook\GithubRequestParser&lt;/span&gt;&lt;br&gt;
        &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(GITHUB_WEBHOOK_SECRET)%'&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Call me back&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we're ready to handle requests, all we need to do is ask GitHub to send us some. &lt;br&gt;
The &lt;a href="https://docs.github.com/en/webhooks/using-webhooks/creating-webhooks" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; is really good so we won't detail the process here. You'll need the endpoint url (&lt;a href="https://example.com/webhook/github" rel="noopener noreferrer"&gt;https://example.com/webhook/github&lt;/a&gt;) and your secret. Just be aware that you can only create webhooks for &lt;strong&gt;resources that you own&lt;/strong&gt;. If you want to be notified on actions performed on a repository you don't own, you'll have to ask the owner to set it up for you. If this is not an option, you'll have to rely on good old &lt;a href="https://en.wikipedia.org/wiki/Polling_(computer_science)" rel="noopener noreferrer"&gt;API polling&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is that even useful ?
&lt;/h3&gt;

&lt;p&gt;You may be tempted to say that all we've done is exposing an endpoint to process a request, and that it could have been done without the webhook component.&lt;br&gt;
And you would be right : you can achieve the same result with a custom controller.&lt;br&gt;&lt;br&gt;
But let's see the benefits of doing it the way we did :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single conf file with minimal and simple configuration for all our exposed endpoints.&lt;/li&gt;
&lt;li&gt;A clean implementation of &lt;code&gt;AbstractRequestParser&lt;/code&gt; to handle request authorization, validation and event dispatching.&lt;/li&gt;
&lt;li&gt;Any service can be turned into a remote event consumer just by using &lt;code&gt;#[AsRemoteEventConsumer]&lt;/code&gt; and &lt;code&gt;ConsumerInterface&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A seamless integration with Symfony Messenger, Notifier, Mailer (and more to come).&lt;/li&gt;
&lt;li&gt;All of the above was done by &lt;strong&gt;running a single command and writing less than 10 lines of code&lt;/strong&gt; 🤯.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's next ?
&lt;/h3&gt;

&lt;p&gt;You may want to take a look at Github's &lt;a href="https://docs.github.com/en/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;Best practices for using webhooks&lt;/a&gt; .&lt;br&gt;
That could make you want to :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add an &lt;code&gt;IpsRequestMatcher&lt;/code&gt; to check if the request is sent from one of GitHub's official IPs.&lt;/li&gt;
&lt;li&gt;Use an async transport to reduce the request process time.&lt;/li&gt;
&lt;li&gt;Handle re-deliveries&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may even want to &lt;a href="https://symfony.com/doc/current/contributing/index.html" rel="noopener noreferrer"&gt;contribute&lt;/a&gt; to Symfony by improving the component or the maker, creating a Bridge to spare some trouble to future developers, ... It's all up to you to make Symfony even better !&lt;/p&gt;

&lt;h3&gt;
  
  
  Tips : Local webhooks
&lt;/h3&gt;

&lt;p&gt;When developing, you may want to receive requests to test your code. You'll need a webhook proxy for this. I would suggest using &lt;a href="https://smee.io/" rel="noopener noreferrer"&gt;smee.io&lt;/a&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start a new channel.&lt;/li&gt;
&lt;li&gt;Copy the link to the "Payload URL" in the GitHub webhook configuration form.&lt;/li&gt;
&lt;li&gt;Download the smee client on your local machine.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;smee -u https://smee.io/thispartisrandom --port 8000 --path /webhhok/github&lt;/code&gt; *&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;For a Symfony application running on localhost:8000&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Accessing the smee url from your browser will allow you to visualize webhooks deliveries : headers, payload, ... and you'll be able to &lt;strong&gt;replay them&lt;/strong&gt;.&lt;br&gt;
You can take advantage of this and copy the payloads and headers to &lt;strong&gt;create fixtures to test your webhooks&lt;/strong&gt; !&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>webhook</category>
      <category>github</category>
      <category>php</category>
    </item>
    <item>
      <title>How to share your TwigComponent with your team ?</title>
      <dc:creator>Matheo Daninos</dc:creator>
      <pubDate>Wed, 15 May 2024 20:40:21 +0000</pubDate>
      <link>https://dev.to/sensiolabs/how-to-share-your-twigcomponent-with-your-team--54b4</link>
      <guid>https://dev.to/sensiolabs/how-to-share-your-twigcomponent-with-your-team--54b4</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Quick Reminders&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This article follows my previous one. If you haven't read it yet, you can do so here: &lt;a href="https://dev.to/sensiolabs/symfony-can-help-you-fall-in-love-with-your-front-end-team-4dak"&gt;Symfony Can Help You Fall in Love with Your Front-End Team&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To give you context, we are developing a small application that lets you listen to various live radio stations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fque827sw262nye4bgxmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fque827sw262nye4bgxmy.png" alt="Our projects"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've adopted the principles from &lt;a href="https://dev.to/webmamba/how-to-integrate-component-architecture-into-symfony-4bjb"&gt;Component Architecture&lt;/a&gt; and, naturally, after designing various components of differing sizes and complexity, we are beginning to establish what is known as a design system.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Overwhelmed by All These Components?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It's common to feel overwhelmed as the number of components grows. In a team setting, or when developing new features, it's crucial to know what components are already available. When we create a new component, we want to share it immediately with our teammates.&lt;/p&gt;

&lt;p&gt;So, how do we manage this?&lt;/p&gt;

&lt;p&gt;Documenting everything? Yes, but it's time-consuming, and let's face it, we're developers—we tend to be a bit lazy.&lt;/p&gt;

&lt;p&gt;But here’s some good news: there’s already a solution in the JavaScript world called &lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is Storybook?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Storybook is an exceptional tool for documenting components. It's specifically designed for this purpose, but it offers much more. It allows us to build components in isolation, test them, and mock them to see how they appear in different states or create specific end-to-end scenarios for each component, among many other features! And if you're still not convinced, just look at how beautiful your documentation can look with Storybook:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5n9bjwm4keecs1xo8he.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5n9bjwm4keecs1xo8he.png" alt="Storybook exemple"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see an example of how I documented our Alert component. This documentation helps my teammates understand how to use the component and the different variations available.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Integrating Storybook with Symfony&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Thanks to contributions from the community, you can now use the StorybookBundle for Symfony, developed by my friend &lt;a href="https://github.com/squrious" rel="noopener noreferrer"&gt;Nicolas&lt;/a&gt; (not Grekas). I’m very excited about this release—not only because it involved a lot of work, but also because this bundle demonstrates that Symfony can match any front-end framework in capabilities.&lt;/p&gt;

&lt;p&gt;To get started, install the bundle with Composer:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="k"&gt;**&lt;/span&gt;composer require sensiolabs/storybook-bundle&lt;span class="k"&gt;**&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then you can use the init command to configure everything for you.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

bin/console storybook:init


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Don’t run away! We gonna use npm but just to run the storybook. This will not have any impact to your project. And the bundle have an integration with AssetMapper, npm is just to start the storybook.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And to finish&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm run storybook


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And just like that you are fully set up! You can go to: &lt;a href="http://localhost:6006/" rel="noopener noreferrer"&gt;http://localhost:6006/&lt;/a&gt; (it can be something else if your port is not available) to admire your storybook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write your first story
&lt;/h2&gt;

&lt;p&gt;Let's get started! The first thing we want to do is show our Alert component in our Storybook.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Alert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../templates/components/Alert.html.twig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sensiolabs/storybook-symfony-webpack5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;twig:Alert color="{{ color }}" size="{{ size }}" position="{{ position }}"&amp;gt;
        {{ message }}
      &amp;lt;/twig:Alert&amp;gt;
  `&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;argTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let me explain what we have here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Alert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../templates/components/Alert.html.twig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We import our component template. Yes, this is Twig code, but thanks to the bundle, it works!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sensiolabs/storybook-symfony-webpack5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we import a &lt;strong&gt;&lt;code&gt;twig&lt;/code&gt;&lt;/strong&gt; function that allows us to write Twig code directly in our JavaScript.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;twig:Alert color="{{ color }}" size="{{ size }}" position="{{ position }}"&amp;gt;
        {{ message }}
      &amp;lt;/twig:Alert&amp;gt;
  `&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;argTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is everything that is common to all stories related to our Alert component.&lt;/p&gt;

&lt;p&gt;We have the &lt;strong&gt;&lt;code&gt;component&lt;/code&gt;&lt;/strong&gt; key that allows us to define our component, the Alert, and a template. We use the &lt;strong&gt;&lt;code&gt;twig&lt;/code&gt;&lt;/strong&gt; function, and everything inside can be any valid Twig code we want. Here, we simply render the Alert component and give this component props: &lt;strong&gt;&lt;code&gt;color&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;size&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;position&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then we define some &lt;strong&gt;&lt;code&gt;argTypes&lt;/code&gt;&lt;/strong&gt; that allow us to describe the controls we want under the story.&lt;/p&gt;

&lt;p&gt;Then we define our first story:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And just like that, we have the following result!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2vkixz0v0ez19u01fcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2vkixz0v0ez19u01fcb.png" alt="First story"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can play with the controls to test your component with different variations.&lt;/p&gt;

&lt;p&gt;Then we can add more stories:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Good job!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So now, if we look at the menu of our Storybook, we have three stories:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxxkka07orzn0oaycleh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxxkka07orzn0oaycleh.png" alt="More stories"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is great, but we can go a bit further. Add the &lt;strong&gt;&lt;code&gt;tags: ['autodocs']&lt;/code&gt;&lt;/strong&gt; to your &lt;strong&gt;&lt;code&gt;export default&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;twig:Alert color="{{ color }}" size="{{ size }}" position="{{ position }}"&amp;gt;
        {{ message }}
      &amp;lt;/twig:Alert&amp;gt;
  `&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;autodocs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And just by adding this tag, Storybook creates a documentation page for us:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feot77yt85mcymsx0rqa0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feot77yt85mcymsx0rqa0.png" alt="Storybook doc"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if that's not crazy enough, you can create a .mdx file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;

import {Canvas, Controls, Meta, Story} from "@storybook/blocks";
import &lt;span class="err"&gt;*&lt;/span&gt; as AlertStories from "./Alert.stories";

&lt;span class="nt"&gt;&amp;lt;Meta&lt;/span&gt; &lt;span class="na"&gt;of=&lt;/span&gt;&lt;span class="s"&gt;{AlertStories}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="gh"&gt;# Alert&lt;/span&gt;

An Alert display information to the user.

&lt;span class="nt"&gt;&amp;lt;Story&lt;/span&gt; &lt;span class="na"&gt;of=&lt;/span&gt;&lt;span class="s"&gt;{AlertStories.Default}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

Here are the properties you can use on the alert component:

&lt;span class="nt"&gt;&amp;lt;Controls&lt;/span&gt; &lt;span class="na"&gt;of=&lt;/span&gt;&lt;span class="s"&gt;{AlertStories.Default}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Error&lt;/span&gt;

For error message you can use:

&lt;span class="nt"&gt;&amp;lt;Canvas&lt;/span&gt; &lt;span class="na"&gt;of=&lt;/span&gt;&lt;span class="s"&gt;{AlertStories.Error}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Success&lt;/span&gt;

For success message you can use

&lt;span class="nt"&gt;&amp;lt;Story&lt;/span&gt; &lt;span class="na"&gt;of=&lt;/span&gt;&lt;span class="s"&gt;{AlertStories.Success}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="sb"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see in this .mdx file, we were able to import our stories into our documentation to make everything playable.&lt;/p&gt;

&lt;p&gt;This is really cool! But you know what? This is only the beginning! We used Storybook to document a simple component, but Storybook can do much more. It can help with developing complex components or testing your components. We still have a lot to learn with Storybook, so stay tuned for the next article!&lt;/p&gt;

&lt;p&gt;Thanks for reading. See you soon!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Symfony can help you fall in love with your front-end team!</title>
      <dc:creator>Matheo Daninos</dc:creator>
      <pubDate>Thu, 11 Apr 2024 16:44:55 +0000</pubDate>
      <link>https://dev.to/sensiolabs/symfony-can-help-you-fall-in-love-with-your-front-end-team-4dak</link>
      <guid>https://dev.to/sensiolabs/symfony-can-help-you-fall-in-love-with-your-front-end-team-4dak</guid>
      <description>&lt;h2&gt;
  
  
  A little context
&lt;/h2&gt;

&lt;p&gt;I'll place myself in the context of a Symfony application. We are a team of 8 developers: 6 dedicated backend Symfony developers and 2 frontend developers, including one who is a junior. &lt;br&gt;
This shows that our team has a strong inclination towards backend development. &lt;strong&gt;The first thing not to do is to underestimate the frontend&lt;/strong&gt;. Don't think that frontend development is easy; on the contrary, it requires expertise - an expertise that our backend developers lack. &lt;br&gt;
Our goal, therefore, will be to take as much weight off the shoulders of our frontend developers as possible, without forcing backend developers to step out of their comfort zone.&lt;/p&gt;

&lt;p&gt;The application I'll use for this example is as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44waor9qhe3do35xg7zp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44waor9qhe3do35xg7zp.png" alt="application homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have a set of radios, multiple pages with different radios, and if you click on any of them, then you can listen to a live stream of that radio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's start working!
&lt;/h2&gt;

&lt;p&gt;So, we start by making the first page and everything goes well. But as we proceed to the next one, we encounter our first problem. Our frontend developers simply reused the code from the first page on the next. They immediately encounter this error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30dcvlmwjtv0wk15lqge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30dcvlmwjtv0wk15lqge.png" alt="error page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is an error we're well acquainted with. We forgot to pass a variable to our Controller. Solving it is simple, but this error illustrates a deeper problem. Our frontend developer is dependent on our backend developers. Each time they make a new page, they have to ask the other developers to prepare a controller with the variables they need. &lt;strong&gt;We want to make our frontend independent&lt;/strong&gt;. We aim to advance the project with as little friction between our backend and frontend devs as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component Architecture
&lt;/h2&gt;

&lt;p&gt;For that, I propose that we cater to the frontend developers by using the component architecture. I won't go back over what component architecture is, but I invite you to read my article on this topic if you haven't already (&lt;a href="https://dev.to/webmamba/how-to-integrate-component-architecture-into-symfony-4bjb"&gt;https://dev.to/webmamba/how-to-integrate-component-architecture-into-symfony-4bjb&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So, we start by breaking down our page into many small components. Our list of radios will be a RadioList component composed of several Radio components. These Radio components are themselves made up of smaller components. To do all this, the frontend developer does not need us at all. They write anonymous component, component only made off a template:&lt;/p&gt;

&lt;h2&gt;
  
  
  RadioCard
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"
        transition ease-in-out delay-150 hover:-translate-y-1
        hover:scale-110 duration-300 aspect-square relative overflow-hidden
        rounded-lg bg-white/50 shadow-lg transition duration-300
        ease-in-out hover:shadow-2xl p-4"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {% block content %}
    {% endblock %}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  RadioCardImage
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="n"&gt;logo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"relative overflow-hidden border-slate-500/50 border rounded aspect-square"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;
                &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full h-full object-cover self-center"&lt;/span&gt;
                &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ logo }}"&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ name }}"&lt;/span&gt;
        &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Radio
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="n"&gt;radio&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;RadioCard&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;RadioCardBackground&lt;/span&gt; &lt;span class="n"&gt;logo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ radio.logo }}"&lt;/span&gt; &lt;span class="n"&gt;radio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ radio.name }}"&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ radio.color }}"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;RadioCardImage&lt;/span&gt; &lt;span class="n"&gt;logo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ radio.logo }}"&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ radio.name }}"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"absolute bottom-2 right-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;PlayerButton&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;RadioCard&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I repeat, to make this list of components, the frontend did not need to write any PHP. &lt;strong&gt;They do all this work with complete autonomy&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend dev it's your time!
&lt;/h2&gt;

&lt;p&gt;Once all these components are made to integrate our list of radios, the backend developers now only have to make a RadioList component, which has the following class:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Twig\Components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Repository\RadioRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\UX\TwigComponent\Attribute\AsTwigComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[AsTwigComponent]&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RadioList&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;RadioRepository&lt;/span&gt; &lt;span class="nv"&gt;$radioRepository&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getRadios&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;radioRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;radioRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findByGenre&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But where it becomes really interesting is for the template, which will now only be this. All the work will have been pre-done by our frontend developers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;radio&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getRadios&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Radio&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;radio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"radio"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is great, we now have a working component able to communicate with our database, that anyone can reuse on every page. And all that while sticking to what we are good at, PHP code.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVA
&lt;/h2&gt;

&lt;p&gt;But we need to go a bit further. For example, for this site, we will need to set up an alert system. The informational alerts will be at the top left in blue, errors at the bottom in bold red, etc. We don't want a component for each variation; we want modular components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgg4gwxmiz7cdj4ejv3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgg4gwxmiz7cdj4ejv3u.png" alt="homepage avec alertes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this in Symfony, we can use CVAs (Component Variant Attributes). Here's how it works:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cva&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'min-w-96 absolute rounded-lg shadow-md bg-slate-200 text-white z-50'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;variants&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'bg-blue-800 shadow-blue-800'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'bg-red-800 shadow-red-800'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'bg-green-800 shadow-green-800'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;'sm'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'p-2 mb-2 text-sm'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'lg'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'p-4 mb-4text-lg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;'top-left'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'top-4 left-4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'top-right'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'top-4 right-4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'bottom-left'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'bottom-4 left-4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'bottom-right'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'bottom-4 right-4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ alert.apply({color, size, position})|tailwind_merge }}"&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"alert"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endblock&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We create our Alert component. At the start of this file, we'll use the cva function. To this function, we'll pass a key array, the first key of which will be 'base' and will take as its value all the CSS classes that will be present in all variations of our components. Next, we'll define our variants. So here, variants on color, size, or position. Then, we'll apply our cva to the div of our alert.&lt;/p&gt;

&lt;p&gt;And just like that, we've created a component with a multitude of possible variations.&lt;/p&gt;

&lt;p&gt;So, quite naturally, we've been able to create a set of components of different sizes, with different variations, from which our developers can draw to implement new features. &lt;strong&gt;We have the beginnings of a design system&lt;/strong&gt;. Working in this way allows our front-end and back-end developers to work without stepping on each other's toes, all while staying on a common codebase. Implementing a new feature is now picking between few components from our design system, we are dangerous!&lt;/p&gt;

&lt;p&gt;Thank you for reading, see you soon for an article on how to document this design system!&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>php</category>
      <category>frontend</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>How to integrate Component Architecture into Symfony?</title>
      <dc:creator>Matheo Daninos</dc:creator>
      <pubDate>Fri, 15 Mar 2024 09:40:02 +0000</pubDate>
      <link>https://dev.to/sensiolabs/how-to-integrate-component-architecture-into-symfony-4bjb</link>
      <guid>https://dev.to/sensiolabs/how-to-integrate-component-architecture-into-symfony-4bjb</guid>
      <description>&lt;p&gt;&lt;strong&gt;Miscommunication in our projects is costly&lt;/strong&gt;. A single misunderstood User Story can result in 3 days of wasted development time. Additionally, when developers do not use the same programming language, it may be necessary to construct APIs to facilitate communication, which can also be expensive. It is important to consider why front-end developers may be hesitant to work with &lt;a href="https://twig.symfony.com/"&gt;Twig&lt;/a&gt; and how this can lead to a disconnect between front-end and back-end development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Front-end developers, like back-end developers, have their own set of habits, principles, and rules&lt;/strong&gt; that make them feel safe, efficient, and at home.&lt;/p&gt;

&lt;p&gt;One of the most important principles is Component Architecture, which serves as the foundation for major JavaScript frameworks such as Svelte, React, and Vue.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is Component Architecture?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;4 main rules define Component Architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Composition&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A page is no longer just a page, but rather a collection of small, reusable components. These components can be assembled to form a page. For example, there could be a component for the title and another for the training list. The training list component could even be composed of smaller components, such as a training card component. The goal is &lt;strong&gt;to create the most atomic components possible&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Independence&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For a component to be infinitely reusable, it should not be aware of its context. &lt;strong&gt;It should function identically when placed on another page&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Properties&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our component must remain independent, but &lt;strong&gt;we can customize it based on the context&lt;/strong&gt;. For instance, we can change the label of a button while keeping its appearance the same by assigning it properties. Properties are attributes that the component will adopt at creation and maintain throughout its life.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;States&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A component can have multiple states. For instance, a button can transition from an active state to a loading state and then to a disabled state. Typically, a state principle is used to achieve this. The difference between a state and a property is that &lt;strong&gt;a property remains constant throughout the lifespan of the component, while a state changes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Does it work with Symfony?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The answer is yes!&lt;/p&gt;

&lt;p&gt;Start by installing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require ux-twig-component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your template folder, create a component folder. Inside the component folder, add a file named Button.html.twig.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that… you have created your first component! Congrats&lt;/p&gt;

&lt;p&gt;Now, how will you use it? Let's assume you want to use it on your homepage. Open your home.html.twig template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the content of your template&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, you have successfully imported a component for the first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Can I include properties for this component?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Sure, you can! Simply go back to your button template and add a label property like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Click me'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, our component has a label property, with a default value 'Click me'. Without this property, using the component would result in an error.&lt;/p&gt;

&lt;p&gt;To assign a property to a component, follow these steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;twig:Button&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;How do we do composition?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If we want our component to contain an icon before our label, let's modify the template of our button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Click me'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endblock&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, I have added a block that I have named content. The term "content" is important because it corresponds to the default block.&lt;/p&gt;

&lt;p&gt;You can now use your button in this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;twig:Button&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;twig:Icon/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/twig:Button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added an Icon component to the Button component by placing it between the tags. This transfers the Icon component to the content block.&lt;/p&gt;

&lt;p&gt;This block is a powerful tool for for precise composition. You can even create your own blocks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Click me'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endblock&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="n"&gt;after_label&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endblock&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;twig:Button&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;twig:Icon/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;twig:block&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"after_label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            +
    &lt;span class="nt"&gt;&amp;lt;/twig:block&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/twig:Button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we use the &amp;lt;twig:block tag to easily refer to the block of our component.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;And what about the states of our component?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You have &lt;strong&gt;two options to manage the states&lt;/strong&gt; of your component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the first option, if you want your component's CSS classes to change based on user actions, you can &lt;strong&gt;use &lt;a href="https://stimulus.hotwired.dev/"&gt;Stimulus&lt;/a&gt;&lt;/strong&gt;, a small JavaScript library that integrates perfectly with Symfony through the &lt;a href="https://symfony.com/bundles/StimulusBundle/current/index.html"&gt;StimulusBundle&lt;/a&gt;. With just a few lines of JavaScript code, you can achieve your goal.&lt;/li&gt;
&lt;li&gt;But imagine having a component with complex states that depend on the back-end. In this case, you have the option of &lt;strong&gt;using &lt;a href="https://ux.symfony.com/live-component"&gt;LiveComponents&lt;/a&gt;&lt;/strong&gt;. I won't go into details, but here is what you need to remember: with very little code, you can perform complex actions on your back-end without reloading the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The present article is meant to give you an overview of how to use Component Architecture with Symfony. I know this article will be read mainly by back-end developers, but spread the word. It is suitable for both back-end and front-end developers. &lt;strong&gt;Even front-end developers can feel at home with Symfony&lt;/strong&gt;. And you, back-end developers, you can continue to have fun while developing a quality front-end.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>frontend</category>
      <category>architecture</category>
      <category>php</category>
    </item>
  </channel>
</rss>
