<?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>Forem: Mark Badolato</title>
    <description>The latest articles on Forem by Mark Badolato (@mbadolato).</description>
    <link>https://forem.com/mbadolato</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%2F107541%2Faf4d13fd-ac97-4f46-8ad1-855eed785722.jpg</url>
      <title>Forem: Mark Badolato</title>
      <link>https://forem.com/mbadolato</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mbadolato"/>
    <language>en</language>
    <item>
      <title>Inject Value Objects Into An Autowired Symfony Service</title>
      <dc:creator>Mark Badolato</dc:creator>
      <pubDate>Thu, 08 Aug 2024 05:00:00 +0000</pubDate>
      <link>https://forem.com/mbadolato/inject-value-objects-into-an-autowired-symfony-service-3an2</link>
      <guid>https://forem.com/mbadolato/inject-value-objects-into-an-autowired-symfony-service-3an2</guid>
      <description>&lt;p&gt;While working on a Symfony project with my team, I needed to inject specific Value Object instances into one of my services. The values themselves, in this particular case, needed to be set from values provided in our &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;I could, of course, just pass the &lt;code&gt;string&lt;/code&gt; values directly into my service and have the service instantiate the Value Objects in the constructor, but I wanted to see if it was possible to configure it in the &lt;code&gt;services.yaml&lt;/code&gt; file and inject the fully-instantiated objects instead. This would allow me to pass those object instances to multiple services and not have to repeat the Value Object creation inside each.&lt;/p&gt;

&lt;p&gt;Here's how I did it...&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Our application utilizes the Twilio SDK. We have various services wrapping the SDK calls, and they need to use our environment-specific configuration values (our company's API Key for each environment, etc.). &lt;/p&gt;

&lt;p&gt;The Twilio API makes use of String Identifiers, or &lt;code&gt;SID&lt;/code&gt;s. Each type of &lt;code&gt;SID&lt;/code&gt; has a different 2-letter prefix associated with it, followed by 32 characters made up of the digits 0 through 9 and the letters A through F (upper and lowercase).&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;ConferenceSid&lt;/code&gt; has the prefix &lt;code&gt;CF&lt;/code&gt; and looks like &lt;code&gt;CFabcdef0123456789abcdef0123456789&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;CallSid&lt;/code&gt; has the prefix &lt;code&gt;CA&lt;/code&gt; and looks like &lt;code&gt;CAabcdef0123456789abcdef0123456789&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;There are other types of &lt;code&gt;SID&lt;/code&gt;s and they all use the same format, only differentiated by the prefix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted to make sure the Value Objects for each &lt;code&gt;SID&lt;/code&gt; type validated that the passed-in value had the proper prefix for that &lt;code&gt;SID&lt;/code&gt; type, along with making sure the string was the correct length and was only made up of the allowed characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Value Objects
&lt;/h2&gt;

&lt;p&gt;Each of my &lt;code&gt;SID&lt;/code&gt; types use the same validation logic and functionality, only diffentiating by the &lt;code&gt;SID&lt;/code&gt; type's prefix, so it makes sense to create a base Trait. This could be an Abstract Class if you prefer. I don't need the concept of a &lt;code&gt;TwilioStringIdentifier&lt;/code&gt; in the app as a parameter type or anything like that, so I prefer a Trait over an Abstract Class here.&lt;/p&gt;

&lt;p&gt;This Trait does define an abstract method &lt;code&gt;getPrefixForSidType()&lt;/code&gt; that each &lt;code&gt;SID&lt;/code&gt; type must implement, providing the proper prefix for that given type. It also performs the validation logic.&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;App\Domain\Model\Twilio\Sid&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;Assert\Assert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;TwilioStringIdentifier&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$sid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;abstract&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;getPrefixForSidType&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;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;$string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&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;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$string&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;__construct&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;$sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Assert&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;that&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sid&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;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPrefixForSidType&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;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;34&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;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/^[a-zA-Z]{2}[0-9a-fA-F]{32}$/'&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;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$sid&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;asString&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;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sid&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;h3&gt;
  
  
  The &lt;code&gt;SID&lt;/code&gt; classes
&lt;/h3&gt;

&lt;p&gt;The Value Object classes representing each of the &lt;code&gt;SID&lt;/code&gt; types are simple. They just need to &lt;code&gt;use&lt;/code&gt; the &lt;code&gt;TwilioStringIdentifier&lt;/code&gt; Trait and to define the proper prefix via the &lt;code&gt;getPrefixForSidType()&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Domain\Model\Twilio\Sid&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;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountSid&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;TwilioStringIdentifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPrefixForSidType&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;'AC'&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;The other &lt;code&gt;SID&lt;/code&gt; type classes are identical except for their defined prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Injecting the instantiated objects
&lt;/h2&gt;

&lt;p&gt;Because these Value Objects will be used all throughout the application and with various values associated, and not just our company's global values, I needed a way to inject into services an object of a specific type that was already instantiated with the values defined in our &lt;code&gt;.env&lt;/code&gt; file&lt;/p&gt;

&lt;p&gt;I knew that Symfony has the ability to define services to be instantiated via a &lt;code&gt;Factory&lt;/code&gt; but had never really seen (that I recall) anything about injecting an object that was the result of a method call from somewhere else. I also knew these &lt;code&gt;Factory&lt;/code&gt; methods could have arguments passed to them, I just wasn't sure how to do this with one Value Object instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Specific Value Object Instance
&lt;/h3&gt;

&lt;p&gt;Symfony's service definition allows you to name each service. Typically it's done with the Service class' name:&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\Path\To\My\Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App\Path\To\My\Service&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, that service name doesn't have to match the class name. It could be &lt;code&gt;app.my_service&lt;/code&gt; or &lt;code&gt;Foo\Bar\Baz\Service&lt;/code&gt; or whatever.&lt;/p&gt;

&lt;p&gt;So, what if I create a service with a unique name that is the instantiated instance of the Value Object I need? I could pass the &lt;code&gt;.env&lt;/code&gt; value in as the argument and then have that object instance to inject into my service classes!&lt;/p&gt;

&lt;h4&gt;
  
  
  services.yaml
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create services named with a Global "namespace"&lt;/span&gt;
&lt;span class="na"&gt;Global\Twilio\Sid\Account&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;factory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;App\Domain\Model\Twilio\Sid\AccountSid'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fromString'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(TWILIO_ACCOUNT_SID)%'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;Global\Twilio\Sid\Api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;factory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;App\Domain\Model\Twilio\Sid\ApiSid'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fromString'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(TWILIO_API_SID)%'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;Global\Twilio\Sid\Application&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;factory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;App\Domain\Model\Twilio\Sid\ApplicationSid'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fromString'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(TWILIO_APP_SID)%'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pass those services (objects) into my Twilio service via their named arguments:&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\Service\Vendor\Twilio\TwilioService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;$accountSid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@Global\Twilio\Sid\Account'&lt;/span&gt;
        &lt;span class="na"&gt;$apiSid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@Global\Twilio\Sid\Api'&lt;/span&gt;
        &lt;span class="na"&gt;$applicationSid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@Global\Twilio\Sid\Application'&lt;/span&gt;
        &lt;span class="na"&gt;$apiSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(TWILIO_API_SECRET)%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now my service class can expect to receive the fully-instantiated Value Object instances:&lt;/p&gt;

&lt;h4&gt;
  
  
  TwilioService
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Service\Vendor\Twilio&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\Domain\Model\Twilio\Sid\AccountSid&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\Domain\Model\Twilio\Sid\ApiSid&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\Domain\Model\Twilio\Sid\ApplicationSid&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;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwilioService&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;AccountSid&lt;/span&gt; &lt;span class="nv"&gt;$accountSid&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;ApiSid&lt;/span&gt; &lt;span class="nv"&gt;$apiSid&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;ApplicationSid&lt;/span&gt; &lt;span class="nv"&gt;$applicationSid&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;string&lt;/span&gt; &lt;span class="nv"&gt;$apiSecret&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;Voila! &lt;/p&gt;

&lt;p&gt;Symfony is flexible enough and intuitive enough that it was simple to figure out how to do this. Since I couldn't find a quick reference for doing this elsewhere, I thought I'd write this up as a reference for Future Me and anyone else who may need to do something similar&lt;/p&gt;

&lt;p&gt;Cheers, and Happy Coding!&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>valueobjects</category>
      <category>php</category>
      <category>twilio</category>
    </item>
  </channel>
</rss>
