<?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: Yonel Ceruto</title>
    <description>The latest articles on DEV Community by Yonel Ceruto (@yceruto).</description>
    <link>https://dev.to/yceruto</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%2Fuser%2Fprofile_image%2F191354%2F6f804ada-a403-40a3-9fce-a63b096579c6.png</url>
      <title>DEV Community: Yonel Ceruto</title>
      <link>https://dev.to/yceruto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yceruto"/>
    <language>en</language>
    <item>
      <title>It's nice to hear that my free-time open-source work has been useful for others building real applications. Feedback like this makes the effort worthwhile.</title>
      <dc:creator>Yonel Ceruto</dc:creator>
      <pubDate>Mon, 23 Mar 2026 14:54:01 +0000</pubDate>
      <link>https://dev.to/yceruto/its-nice-to-hear-that-my-free-time-open-source-work-has-been-useful-for-others-building-real-13bb</link>
      <guid>https://dev.to/yceruto/its-nice-to-hear-that-my-free-time-open-source-work-has-been-useful-for-others-building-real-13bb</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mattleads/mastering-multi-step-forms-in-symfony-74-2g0h" class="crayons-story__hidden-navigation-link"&gt;Mastering Multi-Step Forms in Symfony 7.4&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/mattleads" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3458770%2F5dd4157c-9e79-4084-ad2e-56c3953d8c26.png" alt="mattleads profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mattleads" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Matt Mochalkin
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Matt Mochalkin
                
              
              &lt;div id="story-author-preview-content-3142631" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mattleads" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3458770%2F5dd4157c-9e79-4084-ad2e-56c3953d8c26.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Matt Mochalkin&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/mattleads/mastering-multi-step-forms-in-symfony-74-2g0h" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jan 2&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mattleads/mastering-multi-step-forms-in-symfony-74-2g0h" id="article-link-3142631"&gt;
          Mastering Multi-Step Forms in Symfony 7.4
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/symfony"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;symfony&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/php"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;php&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/mattleads/mastering-multi-step-forms-in-symfony-74-2g0h" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;10&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/mattleads/mastering-multi-step-forms-in-symfony-74-2g0h#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>symfony</category>
      <category>php</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>When “Just Calling a Function” Isn’t That Simple Anymore</title>
      <dc:creator>Yonel Ceruto</dc:creator>
      <pubDate>Thu, 26 Feb 2026 08:51:58 +0000</pubDate>
      <link>https://dev.to/yceruto/when-just-calling-a-function-isnt-that-simple-anymore-4ppb</link>
      <guid>https://dev.to/yceruto/when-just-calling-a-function-isnt-that-simple-anymore-4ppb</guid>
      <description>&lt;p&gt;At some point in almost every project, something small starts to feel heavier than it should.&lt;/p&gt;

&lt;p&gt;You begin with a clean controller that delegates to a handler, then you add validation, then logging. Maybe a transaction. Then consistent error mapping. Nothing dramatic, just normal application growth.&lt;/p&gt;

&lt;p&gt;A few months later, calling a handler isn’t just “calling a handler” anymore.&lt;/p&gt;

&lt;p&gt;Some controllers validate first, others rely on the handler. Some catch domain exceptions and map them to HTTP responses. Others let them bubble up. Background workers behave slightly differently. CLI commands do their own thing.&lt;/p&gt;

&lt;p&gt;The business logic is fine. The execution semantics aren’t.&lt;/p&gt;

&lt;p&gt;And that’s when it becomes clear: invoking a callable is not just a method call. It’s an execution boundary.&lt;/p&gt;

&lt;p&gt;That realization led to &lt;a href="https://github.com/open-solid/callable-invoker" rel="noopener noreferrer"&gt;CallableInvoker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Invokable objects have become common in modern PHP, from single-action controllers to DDD-style handlers using &lt;code&gt;__invoke&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;After using them extensively, I noticed something subtle: they simplify composition, but they also expose inconsistencies in how we execute application logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  A War Story: The Job That Behaved Differently
&lt;/h2&gt;

&lt;p&gt;In a previous project, we had an invokable service that generated invoices. It was used both by an HTTP endpoint and by a nightly background job.&lt;/p&gt;

&lt;p&gt;A few weeks after release, finance noticed that invoices generated overnight didn’t match the ones created manually in the admin panel. The differences were small, mostly rounding, but real.&lt;/p&gt;

&lt;p&gt;After tracing it, we found the cause: the HTTP flow always had an authenticated user, which carried tenant and currency configuration. The scheduled job didn’t. It fell back to defaults.&lt;/p&gt;

&lt;p&gt;The service wasn’t wrong.&lt;/p&gt;

&lt;p&gt;The way we prepared and executed it was inconsistent depending on where it was called from.&lt;/p&gt;

&lt;p&gt;That’s when it became clear the problem wasn’t the business logic, it was the lack of a single, predictable way to invoke it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Way to Execute Callables
&lt;/h2&gt;

&lt;p&gt;In PHP, calling a callable is trivial:&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="nv"&gt;$handler&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But real applications are not trivial.&lt;/p&gt;

&lt;p&gt;What if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the callable has multiple parameters with defaults?&lt;/li&gt;
&lt;li&gt;some arguments should come from a runtime contextual data?&lt;/li&gt;
&lt;li&gt;you want consistent logging or transactions around execution?&lt;/li&gt;
&lt;li&gt;retries or metrics should apply everywhere?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CallableInvoker reframes the problem.&lt;/p&gt;

&lt;p&gt;Instead of asking, “How do I call this?”, it asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How should this be executed, and under what context?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It uses reflection to inspect the callable’s signature, resolves arguments from a provided context, applies defaults, and executes the callable through a pipeline.&lt;/p&gt;

&lt;p&gt;That pipeline can include decorators, small wrappers that add behavior around execution.&lt;/p&gt;

&lt;p&gt;Not inside your business logic. Around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Boilerplate to Focused Handlers
&lt;/h2&gt;

&lt;p&gt;Consider a simple handler:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloHandler&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;string&lt;/span&gt; &lt;span class="nv"&gt;$name&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;$age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"Hello, &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;! You are &lt;/span&gt;&lt;span class="nv"&gt;$age&lt;/span&gt;&lt;span class="s2"&gt; years old."&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;$handler&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;HelloHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually extracting arguments and orchestrating execution:&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;OpenSolid\CallableInvoker\CallableInvoker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$invoker&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;CallableInvoker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$invoker&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;callable&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Evelyn'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Output: Hello, Evelyn! You are 23 years old.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The invoker inspects the signature.&lt;/li&gt;
&lt;li&gt;It resolves parameters from context.&lt;/li&gt;
&lt;li&gt;It applies defaults.&lt;/li&gt;
&lt;li&gt;It executes the callable through registered decorators (if any).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The handler stays focused on business rules. Everything else becomes execution policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decorations: Infrastructure Without Pollution
&lt;/h2&gt;

&lt;p&gt;Decorators are where this becomes useful.&lt;/p&gt;

&lt;p&gt;A decorator can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run logic before execution,&lt;/li&gt;
&lt;li&gt;wrap the callable in a transaction,&lt;/li&gt;
&lt;li&gt;measure execution time,&lt;/li&gt;
&lt;li&gt;translate exceptions,&lt;/li&gt;
&lt;li&gt;or modify the result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of scattering logging, or transaction boundaries across controllers and workers, you define them once.&lt;/p&gt;

&lt;p&gt;Now every invocation follows the same rules. And that consistency is what we were missing in that earlier project.&lt;/p&gt;

&lt;p&gt;In practice, decorators and parameter value resolvers don’t have to be global. They can be grouped and prioritized for specific artifact types. For example, HTTP controllers might have a different decorator stack than message handlers or CLI commands. &lt;/p&gt;

&lt;p&gt;You can define execution profiles where certain behaviors apply only to a subset of callables, and control their order explicitly.&lt;/p&gt;

&lt;p&gt;That means you’re not just centralizing execution, you’re shaping it per context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symfony Integration (Optional, But Powerful)
&lt;/h2&gt;

&lt;p&gt;Although CallableInvoker is framework-agnostic, it integrates naturally with Symfony.&lt;/p&gt;

&lt;p&gt;Symfony already resolves controller arguments, supports invokable console commands, and wires message handlers through Messenger. CallableInvoker can plug into that ecosystem by being registered as a service and configured with autowired decorators and value resolvers.&lt;/p&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can inject &lt;code&gt;CallableInvoker&lt;/code&gt; anywhere in your application.&lt;/li&gt;
&lt;li&gt;Decorators and resolvers can be tagged and auto-registered.&lt;/li&gt;
&lt;li&gt;The Symfony container can participate in argument resolution.&lt;/li&gt;
&lt;li&gt;Different execution profiles can be configured per environment or entry point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows you to extend Symfony’s argument resolution and execution wrapping beyond controllers and commands, bringing the same behavior into your application layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s Not Magic (And It’s Not Free)
&lt;/h2&gt;

&lt;p&gt;A note on tradeoffs: this approach adds indirection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reflection has overhead (usually negligible, but real).&lt;/li&gt;
&lt;li&gt;There’s a mental model to understand.&lt;/li&gt;
&lt;li&gt;Debugging execution flow requires knowing the decorator chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t something I’d use in a throwaway script or a small CRUD app.&lt;/p&gt;

&lt;p&gt;But in long-lived systems, especially those mixing HTTP, CLI, and asynchronous handlers, having a single execution abstraction pays off quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters in Real Systems
&lt;/h2&gt;

&lt;p&gt;As applications evolve, divergence happens quietly.&lt;/p&gt;

&lt;p&gt;When callables don’t share a strict interface, when &lt;code&gt;__invoke&lt;/code&gt; signatures vary, arguments aren’t known at compile time, or inputs depend on the entry point, invocation becomes a dynamic resolution problem, not just a method call.&lt;/p&gt;

&lt;p&gt;CallableInvoker centralizes how callables are prepared and executed. Even when their shape varies, their behavior stays consistent.&lt;/p&gt;

&lt;p&gt;And that consistency is architectural leverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Small Tool That Changes the Shape of Your Code
&lt;/h2&gt;

&lt;p&gt;The biggest improvements in a system often come from refining repeated patterns.&lt;/p&gt;

&lt;p&gt;Calling a callable is one of those patterns.&lt;/p&gt;

&lt;p&gt;Turning that from “just a function call” into a controlled execution boundary doesn’t add features. It adds discipline.&lt;/p&gt;

&lt;p&gt;In our case, it eliminated duplicated glue code, aligned error handling across entry points, and made new handlers simpler to write.&lt;/p&gt;

&lt;p&gt;It didn’t make the system flashy. It made it consistent.&lt;/p&gt;

&lt;p&gt;And for long-lived applications, consistency is what scales.&lt;/p&gt;




&lt;p&gt;Take a tiny detour to 👉 &lt;a href="https://github.com/open-solid/callable-invoker" rel="noopener noreferrer"&gt;https://github.com/open-solid/callable-invoker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it makes your inner developer smile even a little... tap that ⭐ and make the repo’s day! 😄&lt;/p&gt;

</description>
      <category>php</category>
      <category>designpatterns</category>
      <category>architecture</category>
      <category>symfony</category>
    </item>
    <item>
      <title>Generic DBAL Types for Similar Value Objects</title>
      <dc:creator>Yonel Ceruto</dc:creator>
      <pubDate>Fri, 26 Dec 2025 02:51:34 +0000</pubDate>
      <link>https://dev.to/yceruto/generic-dbal-types-for-similar-value-objects-cjc</link>
      <guid>https://dev.to/yceruto/generic-dbal-types-for-similar-value-objects-cjc</guid>
      <description>&lt;p&gt;If you use &lt;a href="https://en.wikipedia.org/wiki/Value_object" rel="noopener noreferrer"&gt;Value Objects&lt;/a&gt; as entity identifiers in PHP, you eventually drown in custom &lt;a href="https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/types.html" rel="noopener noreferrer"&gt;DBAL types&lt;/a&gt;: boilerplate classes, repetitive configuration, and fragile mappings that break silently when someone forgets to register a type or the type name changes.&lt;/p&gt;

&lt;p&gt;This problem shows up consistently in projects built with &lt;a href="https://symfony.com/" rel="noopener noreferrer"&gt;Symfony&lt;/a&gt; and &lt;a href="https://www.doctrine-project.org/" rel="noopener noreferrer"&gt;Doctrine&lt;/a&gt;, especially when following &lt;a href="https://en.wikipedia.org/wiki/Domain-driven_design" rel="noopener noreferrer"&gt;DDD&lt;/a&gt;-style modeling where identifiers are not raw UUIDs but explicit Value Objects.&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\Uid\UuidV7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Uid&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;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;static&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;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UuidV7&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;generate&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;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;static&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;static&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;UuidV7&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&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;toRfc4122&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;private&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&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;$value&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductId&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Uid&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;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;self&lt;/span&gt; &lt;span class="nv"&gt;$other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&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;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$other&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&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;This post explains how to implement a single generic DBAL type that supports these ID Value Objects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is not a primer on Value Objects or Doctrine internals. It assumes prior familiarity with both and focuses strictly on eliminating manual work and improving the DX. For that reason, all code examples are intentionally shortened to emphasize the core ideas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let’s continue and create an entity identified by a first-class Value Object, not a scalar:&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;Doctrine\ORM\Mapping&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="no"&gt;ORM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[ORM\Entity]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Id, ORM\Column]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;ProductId&lt;/span&gt; &lt;span class="nv"&gt;$id&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;That definition is clean, intentional, and fully valid at the domain level. The failure appears only when the ORM tries to bridge the gap to persistence.&lt;/p&gt;

&lt;p&gt;At runtime, Doctrine halts with the following error:&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="o"&gt;[&lt;/span&gt;FAIL] The entity-class App&lt;span class="se"&gt;\E&lt;/span&gt;ntity&lt;span class="se"&gt;\P&lt;/span&gt;roduct mapping is invalid:
 &lt;span class="k"&gt;*&lt;/span&gt; The field &lt;span class="s1"&gt;'App\Entity\Product#id'&lt;/span&gt; has the property &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;'App\Entity\ProductId'&lt;/span&gt; that differs from the metadata field &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt; returned by the &lt;span class="s1"&gt;'string'&lt;/span&gt; DBAL type.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💥 Doctrine has no notion of &lt;code&gt;ProductId&lt;/code&gt; as a domain concept. It sees a PHP class on one side and a scalar database column on the other, and it refuses to guess how the two relate.&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="na"&gt;#[ORM\Id, ORM\Column(type: ProductId::class)]&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;ProductId&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The immediate fix &lt;em&gt;is&lt;/em&gt; to make the mapping explicit so Doctrine no longer has to guess. By providing &lt;code&gt;ProductId::class&lt;/code&gt;, you are effectively telling DBAL: “there exists a column type with this name”, DBAL then looks it up in its internal type registry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Usually you would set a symbolic DBAL type name here (e.g. &lt;code&gt;ORM\Column(type: 'product_id')&lt;/code&gt;). In this case, we're using the Fully-Qualified Class Name (FQCN) of the concrete ID class instead. This is intentional, and the reason will become clear later.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Unknown column &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"App&lt;/span&gt;&lt;span class="se"&gt;\E&lt;/span&gt;&lt;span class="s2"&gt;ntity&lt;/span&gt;&lt;span class="se"&gt;\P&lt;/span&gt;&lt;span class="s2"&gt;roductId"&lt;/span&gt; requested. Any Doctrine &lt;span class="nb"&gt;type &lt;/span&gt;that you use has to be registered with &lt;span class="se"&gt;\D&lt;/span&gt;octrine&lt;span class="se"&gt;\D&lt;/span&gt;BAL&lt;span class="se"&gt;\T&lt;/span&gt;ypes&lt;span class="se"&gt;\T&lt;/span&gt;ype::addType&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; You can get a list of all the known types with &lt;span class="se"&gt;\D&lt;/span&gt;octrine&lt;span class="se"&gt;\D&lt;/span&gt;BAL&lt;span class="se"&gt;\T&lt;/span&gt;ypes&lt;span class="se"&gt;\T&lt;/span&gt;ype::getTypesMap&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; If this error occurs during database introspection &lt;span class="k"&gt;then &lt;/span&gt;you might have forgotten to register all database types &lt;span class="k"&gt;for &lt;/span&gt;a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping&lt;span class="o"&gt;()&lt;/span&gt; or have your custom types implement Type#getMappedDatabaseTypes&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; If the &lt;span class="nb"&gt;type &lt;/span&gt;name is empty you might have a problem with the cache or forgot some mapping information.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💥 It still fails because no such type has been registered. So let’s address the problem by introducing a dedicated DBAL type for these ID value objects.&lt;/p&gt;

&lt;p&gt;First, create a new DBAL type class extending the abstract Type:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UidType&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Type&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;getSQLDeclaration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AbstractPlatform&lt;/span&gt; &lt;span class="nv"&gt;$platform&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&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;$platform&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getGuidTypeDeclarationSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$column&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;convertToPHPValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AbstractPlatform&lt;/span&gt; &lt;span class="nv"&gt;$platform&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Uid&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$class&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;getUidClass&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// other checks ...&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$class&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&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;convertToDatabaseValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AbstractPlatform&lt;/span&gt; &lt;span class="nv"&gt;$platform&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$class&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;getUidClass&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;$value&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nv"&gt;$class&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;$value&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// other checks ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @return class-string&amp;lt;Uid&amp;gt;
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUidClass&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&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;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;lookupName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&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;Then, register this new DBAL type for the missing column type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;doctrine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dbal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;App\Entity\ProductId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App\DBAL\Type\UidType&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class defines a single generic DBAL type able to convert all the values back and forth from multiple identifier Value Objects that all extend a common &lt;code&gt;Uid&lt;/code&gt; base class.&lt;/p&gt;

&lt;p&gt;At the DBAL level, it behaves like any other custom type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getSQLDeclaration()&lt;/code&gt; delegates column definition to the platform, using a GUID-compatible storage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;convertToPHPValue()&lt;/code&gt; transforms the database scalar into a domain identifier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;convertToDatabaseValue()&lt;/code&gt; extracts the scalar value from the identifier before persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, nothing unusual. The critical part is &lt;em&gt;how the concrete identifier class is resolved:&lt;/em&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="cd"&gt;/**
 * @return class-string&amp;lt;Uid&amp;gt;
 */&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUidClass&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&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;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;lookupName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&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;&lt;code&gt;Type::lookupName($this)&lt;/code&gt; returns the registered DBAL type name for the current instance. In this design, that name is not a symbolic alias like &lt;code&gt;product_id&lt;/code&gt;. It is the FQCN of the identifier itself (&lt;code&gt;App\Entity\ProductId&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Because the column mapping uses type &lt;code&gt;ProductId::class&lt;/code&gt;, and the DBAL configuration registers that same FQCN as a type pointing to &lt;code&gt;UidType&lt;/code&gt;, the DBAL type name is the identifier class.&lt;/p&gt;

&lt;p&gt;As a result, the DBAL type instance knows which identifier it represents. &lt;code&gt;getUidClass()&lt;/code&gt; returns the exact concrete &lt;code&gt;Uid&lt;/code&gt; subclass to instantiate, and no subclassing, no switch statements, no per-ID DBAL types.&lt;/p&gt;

&lt;p&gt;Looking to simplify this further?&lt;/p&gt;

&lt;p&gt;DBAL type registration can be automated, and the &lt;code&gt;ORM\Column::$type&lt;/code&gt; value can be inferred directly from the property type, eliminating repetitive configuration and manual wiring.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This solution for generic DBAL types can also be applied to any kind of Value Objects where persistence follows a consistent mapping rule and the concrete class can be resolved at runtime from the DBAL type name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is my last post of the year. Happy holidays!&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>doctrine</category>
      <category>dbal</category>
      <category>dx</category>
    </item>
    <item>
      <title>PHP Callable Decorators</title>
      <dc:creator>Yonel Ceruto</dc:creator>
      <pubDate>Fri, 20 Sep 2024 03:50:58 +0000</pubDate>
      <link>https://dev.to/yceruto/php-callable-decorators-5a79</link>
      <guid>https://dev.to/yceruto/php-callable-decorators-5a79</guid>
      <description>&lt;p&gt;In this post, we're diving into the fascinating world of callable decorators—an advanced technique that can add a new layer of functionality to your functions or methods. Callable decorators can transform the way you think about code organization and reuse.  &lt;/p&gt;

&lt;p&gt;So, grab your favorite beverage, and let’s unlock the potential of PHP decorators together! &lt;/p&gt;

&lt;p&gt;
  Disclaimer (Click here)
  &lt;blockquote&gt;
&lt;p&gt;This article presents a personal workaround for PHP decorators, as PHP doesn't natively support them. The implementation is based on my custom code and practices, not an official PHP feature. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;/p&gt;



&lt;p&gt;Before you continue, please review these prerequisites if you're not familiar with them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/language.types.callable.php" rel="noopener noreferrer"&gt;PHP: Callbacks / Callables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/functions.first_class_callable_syntax.php" rel="noopener noreferrer"&gt;PHP: First Class Callable Syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/language.attributes.overview.php" rel="noopener noreferrer"&gt;PHP: Attributes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://refactoring.guru/design-patterns/decorator" rel="noopener noreferrer"&gt;Design Patterns: Decorator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Are you ready? let's start with the following example:&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;namespace&lt;/span&gt; &lt;span class="nn"&gt;Banking\Account&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;TransferAccountFunds&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;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Account&lt;/span&gt; &lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Account&lt;/span&gt; &lt;span class="nv"&gt;$receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Money&lt;/span&gt; &lt;span class="nv"&gt;$amount&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;// Check if balance is sufficient; throw an exception otherwise&lt;/span&gt;
        &lt;span class="c1"&gt;// Decrease balance with given amount&lt;/span&gt;
        &lt;span class="c1"&gt;// Add transaction record for debit operation&lt;/span&gt;
        &lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Increase balance with given amount&lt;/span&gt;
        &lt;span class="c1"&gt;// Add transaction record for credit operation&lt;/span&gt;
        &lt;span class="nv"&gt;$receiver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&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;Consider this service class that transfers funds between bank accounts. This operation involves multiple database changes, so it must be wrapped in a single &lt;a href="https://en.wikipedia.org/wiki/Atomicity_(database_systems)" rel="noopener noreferrer"&gt;atomic transaction&lt;/a&gt; to ensure all data is either fully saved or none at all if an exception occurs.&lt;/p&gt;

&lt;p&gt;A feasible approach is to inject your database connection service, wrap your code explicitly in a &lt;code&gt;try/catch&lt;/code&gt; block, and manually handle the "start transaction," "commit," and "rollback" statements.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransferAccountFunds&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="kt"&gt;DatabaseConnection&lt;/span&gt; &lt;span class="nv"&gt;$conn&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;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Account&lt;/span&gt; &lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Account&lt;/span&gt; &lt;span class="nv"&gt;$receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Money&lt;/span&gt; &lt;span class="nv"&gt;$amount&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;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$receiver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&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;conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&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;conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nv"&gt;$e&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;This method works but mixes persistence concerns with your business logic, complicating your code. Plus, you’d need to repeat it for every similar use case, reducing maintainability.&lt;/p&gt;

&lt;p&gt;So, how can we accomplish this without modifying our business logic? 🥁&lt;/p&gt;




&lt;h2&gt;
  
  
  Callable Decorator to the Rescue!!
&lt;/h2&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%2Fz0p775amuf8z5zh4b1nc.png" 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%2Fz0p775amuf8z5zh4b1nc.png" alt=" " width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since PHP 8, #[attributes] let you add structured metadata to code elements like methods, which can be inspected at runtime. They act as an embedded configuration language, decoupling the generic implementation of a feature from its concrete use.&lt;/p&gt;

&lt;p&gt;Combining attributes with the decorator pattern lets us elegantly manage the transactional requirement. But first, let’s dive into what a callable decorator is.&lt;/p&gt;

&lt;p&gt;A "Callable Decorator" adds new behaviors to a callable by wrapping it in another callable that includes the additional behaviors. It’s a simple concept, but it can be a bit tricky to grasp. Let’s break down how to create one.&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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;decorate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;callable&lt;/span&gt; &lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;callable&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;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// do something before&lt;/span&gt;

        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// do something after&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&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;A callable decorator takes a callable as an argument, wraps it in a new function (the “wrapping phase”), and then adds additional functionality before or after the function call (the “enhancement and call phase”).&lt;/p&gt;

&lt;p&gt;By returning a callable, this approach requires executing the decoration first and then calling the decorated function as if it were the original.&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;$func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&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;$x&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="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nv"&gt;$func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decorate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// decoration phase&lt;/span&gt;
&lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&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="c1"&gt;// enhancement and calling phase.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to give it a try? Check this out: &lt;a href="https://3v4l.org/bbNql" rel="noopener noreferrer"&gt;https://3v4l.org/bbNql&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Callable Decorators are especially useful in situations where you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decouple functionality from its actual usage without using a interface to achieve it.&lt;/li&gt;
&lt;li&gt;Perform an action before or after a function call (e.g. logging, security checks, validation).&lt;/li&gt;
&lt;li&gt;Conditionally skip the function call by returning early (e.g. caching).&lt;/li&gt;
&lt;li&gt;Modify the function’s result (e.g. serialization, normalization, formatting).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike traditional "Object Decorators", Callable Decorators focus on functions and methods only, making them ideal for modifying specific behaviors without altering the entire object.&lt;/p&gt;

&lt;p&gt;This technique consists of two main elements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The decorator attribute&lt;/strong&gt;: Holds all metadata and links a callable to its decorator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The decorator itself&lt;/strong&gt;: Takes a callable as an argument and returns another callable with additional functionality.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Shall we create our transactional decorator using this approach? Let’s do it!&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="na"&gt;#[Attribute(Attribute::TARGET_METHOD)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transactional&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;decoratedBy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;TransactionalDecorator&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="p"&gt;}&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;TransactionalDecorator&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="kt"&gt;DatabaseConnection&lt;/span&gt; &lt;span class="nv"&gt;$conn&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;decorate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;callable&lt;/span&gt; &lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;callable&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;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;mixed&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;conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$args&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;conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;commit&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;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&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;conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nv"&gt;$e&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far, we’ve built a generic decorator that can wrap any callable in a transactional database operation, no matter its origin. Next, we’ll use the &lt;code&gt;#[Transactional]&lt;/code&gt; attribute to specify which callable should be wrapped.&lt;/p&gt;

&lt;p&gt;Let’s decorate our initial example:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransferAccountFunds&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Transactional]&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;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Account&lt;/span&gt; &lt;span class="nv"&gt;$sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Account&lt;/span&gt; &lt;span class="nv"&gt;$receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Money&lt;/span&gt; &lt;span class="nv"&gt;$amount&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;$sender&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$receiver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&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;Is that all? Not quite. We’re missing the step where the attribute is read at runtime and the linked decorator is applied to ensure the &lt;code&gt;TransferAccountFunds::execute()&lt;/code&gt; method is correctly wrapped before it’s called.&lt;/p&gt;

&lt;p&gt;The proper implementation of this step will be covered in the next article of this series, where we’ll see how to apply callable decorators to enhance your code’s functionality, providing a flexible way to manage cross-cutting concerns. &lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;




&lt;p&gt;Because I love the Symfony framework, I’m introducing this approach for Symfony Controllers through &lt;a href="https://github.com/symfony/symfony/pull/58076" rel="noopener noreferrer"&gt;a new Decorator component&lt;/a&gt;. However, you can find this integration already available in two PHP libraries: &lt;a href="https://github.com/yceruto/decorator" rel="noopener noreferrer"&gt;yceruto/decorator&lt;/a&gt; and &lt;a href="https://github.com/yceruto/decorator-bundle" rel="noopener noreferrer"&gt;yceruto/decorator-bundle&lt;/a&gt; (compatible with Symfony 6.4 or superior).&lt;/p&gt;

</description>
      <category>php</category>
      <category>callable</category>
      <category>decorator</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Bundling Your Symfony UX Twig Components</title>
      <dc:creator>Yonel Ceruto</dc:creator>
      <pubDate>Mon, 29 Jul 2024 06:52:05 +0000</pubDate>
      <link>https://dev.to/yceruto/bundling-your-symfony-ux-twig-components-4997</link>
      <guid>https://dev.to/yceruto/bundling-your-symfony-ux-twig-components-4997</guid>
      <description>&lt;p&gt;Have you heard about Symfony UX Twig Components? If not, I recommend starting by reading the &lt;a href="https://symfony.com/bundles/ux-twig-component/current/index.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; to get firsthand understanding. However, in this article, I will give a brief introduction and then quickly jump to today's main topic: How to bundle Twig Components for reuse in your applications.&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%2F40jsk4mpbhuls8zrote5.png" 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%2F40jsk4mpbhuls8zrote5.png" alt=" " width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Symfony UX components are divided into two main categories: those consisting of a PHP class (controller) plus a Twig template (user interface) plus optional assets (JavaScript, CSS, etc.), which I will call "FullStack Components" in this article; and those made up solely of a user interface (Twig template and assets), referred to as "UI-only Components," also known as "Anonymous Twig Components."&lt;/p&gt;

&lt;p&gt;FullStack components, thanks to their controller (the PHP class), can fetch information from a data source and render it on the user interface (the Twig template), making them incredibly dynamic tools. Think of them as compact, reusable controllers, each with a specific responsibility and UI. For example, a table of recommended products could be displayed both on your site's homepage and on individual product pages.&lt;/p&gt;

&lt;p&gt;In contrast, the UI-only Components don't have a controller (the PHP class) and are useful when you need to reuse a user interface whose content is static or comes from an unknown data source. For example, a button with a &lt;code&gt;label&lt;/code&gt; property or an alert message.&lt;/p&gt;

&lt;p&gt;There are also other subcategories of UX components, such as "Live Components," and components whose UI is not rendered using a Twig template but is instead generated at runtime using pure PHP like UX Icon component. I won’t delve deeper into these components here, but you can find more information in the Symfony UX documentation.&lt;/p&gt;

&lt;p&gt;Now let's get to the meat of the matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bundling FullStack Components
&lt;/h2&gt;

&lt;p&gt;Following the &lt;a href="https://github.com/symfony/recipes/blob/main/symfony/ux-twig-component/2.13/config/packages/twig_component.yaml" rel="noopener noreferrer"&gt;default recipe configuration&lt;/a&gt;, the &lt;code&gt;twig_component&lt;/code&gt; extension includes a &lt;code&gt;defaults&lt;/code&gt; config. This allows us (the bundles) to prepend new namespaces/paths and register our own FullStack components.&lt;/p&gt;

&lt;p&gt;Here is an example of the structure you could follow to achieve this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acme-bundle/
├── assets/
│   ├── dist/
│   │   ├── controller.js
│   │   └── style.min.css
│   └── package.json
├── src/
│   └── Twig/
│       └── Component/
│           └── Search.php
└── templates/
    └── components/
        └── Search.html.twig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;components/&lt;/code&gt; directory is not mandatory for FullStack components; they can be placed at the root of the templates if your bundle focuses solely on UX components. However, it’s necessary for UI-only components. Therefore, I’ve placed both types under the same directory for consistency.&lt;/p&gt;

&lt;p&gt;Here is the configuration you need to add to make the FullStack component work:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AcmeBundle&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractBundle&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;prependExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ContainerConfigurator&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ContainerBuilder&lt;/span&gt; &lt;span class="nv"&gt;$builder&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;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prependExtensionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'twig_component'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'defaults'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'AcmeBundle\Twig\Component\\'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'template_directory'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'@Acme/components/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'name_prefix'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Acme'&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the term &lt;code&gt;defaults&lt;/code&gt; in the configuration might be a bit misleading (terms like &lt;code&gt;mappings&lt;/code&gt; or &lt;code&gt;namespaces&lt;/code&gt; might be clearer), this section allows you to register your own Twig components namespace and the necessary options to locate them.&lt;/p&gt;

&lt;p&gt;Here's a trick: the &lt;code&gt;template_directory&lt;/code&gt; isn't actually a directory path but rather a prefix for Twig template paths. It's unclear if this is intentional, but that's how it's used in the code. By taking advantage of this, I prefixed the path with the &lt;code&gt;@Acme/&lt;/code&gt; namespace. It worked perfectly!&lt;/p&gt;

&lt;p&gt;Ensure the namespace you use matches the one assigned to your bundle by the &lt;code&gt;TwigBundle&lt;/code&gt;. You can use the &lt;code&gt;debug:twig&lt;/code&gt; command to find this out. If there's a mismatch, Twig won’t be able to locate your component templates.&lt;/p&gt;

&lt;p&gt;For consistency, also use this namespace for the &lt;code&gt;name_prefix&lt;/code&gt; option. If your &lt;code&gt;Search.php&lt;/code&gt; component controller doesn't directly specify a component &lt;code&gt;name&lt;/code&gt;, it will default to using this prefix. For instance, the component name would be &lt;code&gt;Acme:Search&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;If your UX component requires certain assets and your application uses the AssetMapper component, the simplest approach is to prepend the asset path as well:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AcmeBundle&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractBundle&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;prependExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ContainerConfigurator&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ContainerBuilder&lt;/span&gt; &lt;span class="nv"&gt;$builder&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;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prependExtensionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'framework'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'asset_mapper'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'paths'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/../../assets/dist'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'@symfony/ux-acme-component'&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&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 will guarantee that your assets are imported correctly and associated with your component. And that wraps up everything about FullStack Twig Components! &lt;/p&gt;

&lt;p&gt;From here, you can reuse your components across any application using either syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;twig:Acme:Search&amp;gt;&amp;lt;/twig:Acme:Search&amp;gt;&lt;/span&gt;  

or  

&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Acme:Search'&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;h2&gt;
  
  
  Bundling UI-only Components
&lt;/h2&gt;

&lt;p&gt;Handling UI-only components is more complex because there is only one &lt;code&gt;anonymous_template_directory&lt;/code&gt;. I believe we can improve this by allowing the registration of custom paths, similar to what we do with FullStack components. However, the term used here is misleading—it's not a directory path but a Twig template prefix. This prefix is used to concatenate and locate the template.&lt;/p&gt;

&lt;p&gt;UI-only components follow a different convention from FullStack components, so they can't be located in the same way. Let’s look at an example to illustrate this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;twig:Acme:Alert&amp;gt;&amp;lt;/twig:Acme:Alert&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if you store the &lt;code&gt;Alert&lt;/code&gt; component in the &lt;code&gt;&amp;lt;acme-bundle&amp;gt;/templates/components/&lt;/code&gt; directory, you will encounter the following error: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error rendering "Acme:Alert" component: Unknown component "Acme:Alert". And no matching anonymous component template was found.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The issue arises because the loading mechanism for UI-only components relies on the &lt;code&gt;anonymous_template_directory&lt;/code&gt; configuration, which by default points to the &lt;code&gt;templates/components/&lt;/code&gt; directory in your application's path, not in the bundle path as you might expect. So, what can we do about this?&lt;/p&gt;

&lt;p&gt;Behind the scenes, the component template finder service attempts to locate the template at &lt;code&gt;templates/components/Acme/Alert.html.twig&lt;/code&gt;. Naturally, this template doesn't exist at the application location.&lt;/p&gt;

&lt;p&gt;To address this issue, start by creating a new directory named &lt;code&gt;Acme/&lt;/code&gt; within the existing &lt;code&gt;templates/components/&lt;/code&gt; directory in your bundle. Then, place the &lt;code&gt;Alert.html.twig&lt;/code&gt; template inside this new directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acme-bundle/
└── templates/
    └── components/
        ├── Acme/
        │   └── Alert.html.twig
        └── Search.html.twig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adjustment partially resolves the issue. However, we still need to ensure that the loader searches within the bundle path. Fortunately, there is a Twig feature that can assist us with this.&lt;/p&gt;

&lt;p&gt;In Twig, if a template name lacks a namespace, the loader will search all registered paths that don't specify a namespace. You can identify these paths by running the &lt;code&gt;debug:twig&lt;/code&gt; command (&lt;code&gt;(None)&lt;/code&gt; namespace).&lt;/p&gt;

&lt;p&gt;So, how about registering unnamespaced Twig paths within your bundle's context? This trick lets Twig access the bundle's templates without needing a namespace. However, keep in mind this is more of a workaround to help the loader locate your bundle templates, specifically those under the &lt;code&gt;templates/components/Acme/&lt;/code&gt; directory:&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="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AcmeBundle&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractBundle&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;prependExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ContainerConfigurator&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ContainerBuilder&lt;/span&gt; &lt;span class="nv"&gt;$builder&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;// ...&lt;/span&gt;

        &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prependExtensionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'paths'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'templates/bundles/AcmeBundle/'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&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="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/templates/'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="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;Great! Now you can render your &lt;code&gt;&amp;lt;twig:Acme:Alert&amp;gt;&lt;/code&gt; component. Just refresh the page, and it will display the component from &lt;code&gt;&amp;lt;acme-bundle&amp;gt;/templates/components/Acme/Alert.html.twig&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overriding Your Bundled UX Components
&lt;/h2&gt;

&lt;p&gt;Sometimes, your generic UX components require UI tweaks. Consider a scenario where you're using a Bootstrap or Tailwind bundle that packages common UX components. Instead of creating entirely new components, including PHP controllers, you can simply override the Twig template (UI) by following the same &lt;code&gt;TwigBundle&lt;/code&gt; convention.&lt;/p&gt;

&lt;p&gt;The overriding rule works on a priority basis: the first template found takes precedence. That's why we registered the path &lt;code&gt;'templates/bundles/AcmeBundle/' =&amp;gt; null&lt;/code&gt; at a high position in the prepend method.&lt;/p&gt;

&lt;p&gt;Simply place your customized templates in &lt;code&gt;templates/bundles/AcmeBundle/&lt;/code&gt;, following the same organization as the bundle, and the Twig loader will handle the rest, ensuring your modifications are used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-app/
└── templates/
    └── bundles/
        └── AcmeBundle/
            └── components/
                ├── Acme/
                │   └── Alert.html.twig
                └── Search.html.twig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, components are designed to be reusable. Avoid creating a UX component unless you're sure it will be used at least twice. It's always better to refactor later, extract, and create a component when there's a real need for it.&lt;/p&gt;

&lt;p&gt;Happy hacking! Cheers!&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>ux</category>
      <category>bundle</category>
      <category>php</category>
    </item>
    <item>
      <title>Why OOP is Your Old New Best Friend: The Date Distance Problem</title>
      <dc:creator>Yonel Ceruto</dc:creator>
      <pubDate>Sat, 13 Jul 2024 17:46:41 +0000</pubDate>
      <link>https://dev.to/yceruto/why-oop-is-your-old-and-new-best-friend-the-date-distance-problem-1059</link>
      <guid>https://dev.to/yceruto/why-oop-is-your-old-and-new-best-friend-the-date-distance-problem-1059</guid>
      <description>&lt;p&gt;Calculating the distance between two dates is relatively straightforward. There are multiple ways. Think about it for a moment... If you like functional programming, your first thought might look 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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;date_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$origin&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;That's it! Definition and implementation. It gets the job done! Simple, right? But hold on... &lt;em&gt;simple isn't always right!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Trying to reuse this function feels like using someone else's toothbrush: it might seem okay in a hurry, but you're not really sure what issues it might cause.&lt;/p&gt;

&lt;p&gt;Developers usually read just the first few lines:&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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;date_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that definition is self-explanatory, nobody cares about the implementation. But is it? Is the return value in seconds, milliseconds, or something else? What about the date arguments, &lt;code&gt;$origin&lt;/code&gt; and &lt;code&gt;$destination&lt;/code&gt;? They seem to be dates, but it's not entirely clear. Confusing, right?&lt;/p&gt;

&lt;p&gt;Ensuring clarity on what every function does, what inputs it needs, and what it returns isn't easy. Even for you, because trust me, you'll eventually forget the details of your own code.&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%2F4xdlfxghlj549v9qix82.png" 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%2F4xdlfxghlj549v9qix82.png" alt=" " width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wouldn't it be better to have a specific return type called &lt;code&gt;Seconds&lt;/code&gt;? Absolutely! It's not just any random number, and &lt;em&gt;that distinction matters&lt;/em&gt;. &lt;em&gt;We want to clearly indicate that the distance will be in &lt;code&gt;Seconds&lt;/code&gt;.&lt;/em&gt; Let's update the function to reflect 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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;date_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DateTime&lt;/span&gt; &lt;span class="nv"&gt;$origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;DateTime&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, instead of any datetime string, we ask for a &lt;a href="https://www.php.net/manual/en/class.datetime" rel="noopener noreferrer"&gt;&lt;code&gt;DateTime&lt;/code&gt;&lt;/a&gt; object representing a valid datetime. And instead of returning just any integer, we return the number of &lt;code&gt;Seconds&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Seconds&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="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$distance&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;date_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DateTime&lt;/span&gt; &lt;span class="nv"&gt;$origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;DateTime&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Seconds&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTimestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$origin&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTimestamp&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;Seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$distance&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;The implementation looks a lot like the first version, with small changes. We're using objects and methods instead of &lt;a href="https://www.php.net/manual/en/function.is-scalar.php" rel="noopener noreferrer"&gt;scalar&lt;/a&gt; values and functions! &lt;em&gt;The responsibility of parsing the datetime is handled by requiring a &lt;code&gt;DateTime&lt;/code&gt; instance.&lt;/em&gt; Nice! And I'm completely sure about the return type, no doubts, it's in seconds.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Seconds&lt;/code&gt; object isn't just a data container; it includes behavior through methods. You can add methods for time unit conversions like &lt;code&gt;toMinutes()&lt;/code&gt; or &lt;code&gt;toDays()&lt;/code&gt;, and maybe an &lt;code&gt;asAbsolute()&lt;/code&gt; method to always return a positive distance. Super!&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%2F41pmlkilb3s8vdtszrqm.png" 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%2F41pmlkilb3s8vdtszrqm.png" alt=" " width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, there's another way to think about this solution. Read it again:&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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;date_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DateTime&lt;/span&gt; &lt;span class="nv"&gt;$origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;DateTime&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Seconds&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTimestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$origin&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTimestamp&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;Seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$distance&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;Any other improvements? Let's look at the usage snippet:&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;$seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dateA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$dateB&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our heads, it reads as "Compute the date distance between date A and date B in seconds," which is correct, but it could simply be "Compute the date A distance to date B in seconds." Still pondering? There's a principle that has always helped me reason about object-oriented solutions: &lt;a href="https://martinfowler.com/bliki/TellDontAsk.html" rel="noopener noreferrer"&gt;"&lt;em&gt;Tell, Don't Ask.&lt;/em&gt;"&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Tell, Don't Ask" principle states that Object-orientation combines data with behavior (methods). Instead of requesting data from an object, we should tell the object what to do.&lt;/p&gt;

&lt;p&gt;In this sense, we should never create a global function to solve a problem unless there is no relevant Object concept around it. So, this is what we want to achieve now:&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;$seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dateA&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;distanceTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dateB&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;distanceTo()&lt;/code&gt; method doesn't exist in the &lt;code&gt;DateTime&lt;/code&gt; class, but we can extend &lt;code&gt;DateTime&lt;/code&gt; and create our own &lt;code&gt;DatePoint&lt;/code&gt; class:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatePoint&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DateTime&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;distanceTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;self&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Seconds&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTimestamp&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;getTimestamp&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;Seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$distance&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;Note the &lt;code&gt;self $destination&lt;/code&gt; argument and the &lt;code&gt;$this-&amp;gt;getTimestamp()&lt;/code&gt; call. This approach keeps the function relevant to the &lt;code&gt;DatePoint&lt;/code&gt; object, ensuring that the calculation of the distance between two dates is neatly encapsulated within the object-oriented design.&lt;/p&gt;

&lt;p&gt;This way, we leverage the full power of object-oriented programming by bundling data and behavior together.&lt;/p&gt;

&lt;p&gt;As a challenge, I invite you to develop a complete solution that covers all the issues explained here, including validation constraints around the origin and destination dates.&lt;/p&gt;

&lt;p&gt;If you enjoyed this content, please leave a like—it motivates me to write more. Also, share your feedback if you have a different perspective or want to elaborate on this topic.&lt;/p&gt;

&lt;p&gt;Ciao!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>oop</category>
      <category>beginners</category>
      <category>php</category>
    </item>
    <item>
      <title>Symfony App Config in 2 steps</title>
      <dc:creator>Yonel Ceruto</dc:creator>
      <pubDate>Wed, 22 May 2024 21:27:34 +0000</pubDate>
      <link>https://dev.to/yceruto/symfony-app-config-in-2-steps-26dl</link>
      <guid>https://dev.to/yceruto/symfony-app-config-in-2-steps-26dl</guid>
      <description>&lt;p&gt;Customizing your Symfony application often involves tweaking configurations. For instance, enabling &lt;a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery" rel="noopener noreferrer"&gt;CSRF&lt;/a&gt; protection requires editing the &lt;code&gt;framework.yaml&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;csrf_protection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These configurations, stored in the &lt;code&gt;config/packages&lt;/code&gt; directory, come from third-party packages known as "Bundles." Each bundle can add its own configuration, identified by an extension alias (e.g. &lt;code&gt;framework&lt;/code&gt;) and its settings (e.g. &lt;code&gt;csrf_protection: true&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;But what if you want to add custom configurations for your own application? Do you need to create a bundle for that? Not at all. Let me show you how to add your own application configuration in just 2 simple steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create an Extension Class
&lt;/h2&gt;

&lt;p&gt;First, create an extension class to define your configuration. This class extends the &lt;code&gt;Symfony\Component\DependencyInjection\Extension\AbstractExtension&lt;/code&gt; class and implements the &lt;code&gt;configure()&lt;/code&gt; method:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Extension&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\Config\Definition\Configurator\DefinitionConfigurator&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\DependencyInjection\ContainerBuilder&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\DependencyInjection\Extension\AbstractExtension&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\DependencyInjection\Loader\Configurator\ContainerConfigurator&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;AppExtension&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractExtension&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;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DefinitionConfigurator&lt;/span&gt; &lt;span class="nv"&gt;$definition&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;$definition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rootNode&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;children&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;booleanNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'some_toggle'&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;defaultFalse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&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;loadExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ContainerConfigurator&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ContainerBuilder&lt;/span&gt; &lt;span class="nv"&gt;$builder&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;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;parameters&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;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.some_toggle'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'some_toggle'&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;getAlias&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'app'&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, we've defined a configuration with a single boolean value called &lt;code&gt;some_toggle&lt;/code&gt; (default is &lt;code&gt;false&lt;/code&gt;). This is used later by the &lt;code&gt;loadExtension()&lt;/code&gt; method to create a container parameter that can be injected into your services or controllers.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getAlias()&lt;/code&gt; method is optional. By default, Symfony will guess the alias from the class name, so &lt;code&gt;AppExtension&lt;/code&gt; becomes &lt;code&gt;app&lt;/code&gt;. You only need to override it if you want to use a different extension alias, e.g. &lt;code&gt;project_name&lt;/code&gt; instead of &lt;code&gt;app&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Register the Extension Class
&lt;/h2&gt;

&lt;p&gt;Next, register this extension class in the container within your Kernel class &lt;code&gt;build()&lt;/code&gt; method, so the container knows about it:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App&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\Extension\AppExtension&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\Bundle\FrameworkBundle\Kernel\MicroKernelTrait&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\DependencyInjection\ContainerBuilder&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\HttpKernel\Kernel&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;BaseKernel&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;Kernel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseKernel&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;MicroKernelTrait&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;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ContainerBuilder&lt;/span&gt; &lt;span class="nv"&gt;$container&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;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;registerExtension&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;AppExtension&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 that’s it! Now you can add your own application configuration in a new &lt;code&gt;config/packages/app.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;some_toggle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this guide helps you add custom configurations to your Symfony applications. If you have any questions, feel free to ask in the comments below.&lt;/p&gt;

</description>
      <category>php</category>
      <category>symfony</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
