<?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: O ji nwayo e je</title>
    <description>The latest articles on Forem by O ji nwayo e je (@mmayboy_).</description>
    <link>https://forem.com/mmayboy_</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%2F220958%2Fd3e18021-6c7d-4aa7-a2dc-d8e6223bf219.jpg</url>
      <title>Forem: O ji nwayo e je</title>
      <link>https://forem.com/mmayboy_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mmayboy_"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>O ji nwayo e je</dc:creator>
      <pubDate>Sat, 20 Sep 2025 07:53:24 +0000</pubDate>
      <link>https://forem.com/mmayboy_/-3nd4</link>
      <guid>https://forem.com/mmayboy_/-3nd4</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/progrium/building-your-own-ngrok-in-130-lines-2lif" class="crayons-story__hidden-navigation-link"&gt;Building your own Ngrok in 130 lines&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="/progrium" 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%2F11207%2Ffa43f37a-3d44-40ac-9610-cd9cd1494f16.jpg" alt="progrium profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/progrium" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jeff Lindsay
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jeff Lindsay
                
              
              &lt;div id="story-author-preview-content-701797" 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="/progrium" 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%2F11207%2Ffa43f37a-3d44-40ac-9610-cd9cd1494f16.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jeff Lindsay&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/progrium/building-your-own-ngrok-in-130-lines-2lif" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 27 '21&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/progrium/building-your-own-ngrok-in-130-lines-2lif" id="article-link-701797"&gt;
          Building your own Ngrok in 130 lines
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&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;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&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/progrium/building-your-own-ngrok-in-130-lines-2lif" 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/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.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;146&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/progrium/building-your-own-ngrok-in-130-lines-2lif#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              4&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>programming</category>
      <category>showdev</category>
      <category>go</category>
    </item>
    <item>
      <title>Inspiring for me as a tiny framework maintainer</title>
      <dc:creator>O ji nwayo e je</dc:creator>
      <pubDate>Mon, 18 Aug 2025 17:34:06 +0000</pubDate>
      <link>https://forem.com/mmayboy_/inspiring-for-me-as-a-tiny-framework-maintainer-1jl9</link>
      <guid>https://forem.com/mmayboy_/inspiring-for-me-as-a-tiny-framework-maintainer-1jl9</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/our-web-framework-reached-9000-stars-on-github-9000-jij" class="crayons-story__hidden-navigation-link"&gt;🎉 Our web framework reached 9,000 stars on GitHub! ⭐️ 9️⃣0️⃣0️⃣0️⃣ ⭐️&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 class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.dev.to%2Fcdn-cgi%2Fimage%2Fwidth%3D90%2Cheight%3D90%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto%2Fhttps%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Forganization%252Fprofile_image%252F3369%252Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/matijasos" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F331796%2F5d3fd56d-440c-437c-bcda-152c482774b9.jpeg" alt="matijasos profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/matijasos" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Matija Sosic
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Matija Sosic
                
              
              &lt;div id="story-author-preview-content-1775122" 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="/matijasos" 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%2F331796%2F5d3fd56d-440c-437c-bcda-152c482774b9.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Matija Sosic&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/our-web-framework-reached-9000-stars-on-github-9000-jij" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 5 '24&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/wasp/our-web-framework-reached-9000-stars-on-github-9000-jij" id="article-link-1775122"&gt;
          🎉 Our web framework reached 9,000 stars on GitHub! ⭐️ 9️⃣0️⃣0️⃣0️⃣ ⭐️
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/news"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;news&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&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/wasp/our-web-framework-reached-9000-stars-on-github-9000-jij" 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/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/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.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;283&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/wasp/our-web-framework-reached-9000-stars-on-github-9000-jij#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              45&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;
            3 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>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>news</category>
    </item>
    <item>
      <title>A Practical Guide to Robust Webhooks in Suphle</title>
      <dc:creator>O ji nwayo e je</dc:creator>
      <pubDate>Sun, 06 Apr 2025 23:10:46 +0000</pubDate>
      <link>https://forem.com/mmayboy_/a-practical-guide-to-robust-webhooks-in-suphle-343g</link>
      <guid>https://forem.com/mmayboy_/a-practical-guide-to-robust-webhooks-in-suphle-343g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article proposes a more elegant way to implement webhooks. The code examples are written in PHP using &lt;a href="https://angry-cray-9c191b.netlify.app/" rel="noopener noreferrer"&gt;Suphle&lt;/a&gt;, a polished framework for building modern full-stack PHP applications.&lt;/p&gt;

&lt;p&gt;A robust webhook implementation—such as consuming a payment gateway—requires several key elements, which I’ll walk through below. But first, let’s examine some pseudocode representing the ideal outcome. Any implementation that goes far beyond these essentials risks becoming boilerplate-heavy and unnecessarily complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  Target Behavior
&lt;/h2&gt;

&lt;p&gt;A typical implementation of our webhook integration might follow this flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initiate payment&lt;/li&gt;
&lt;li&gt;Receive a response&lt;/li&gt;
&lt;li&gt;Parse and persist relevant data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We generally expect this sequence to complete successfully—requests fire as expected, payloads are intact, and the application responds meaningfully. But real-world systems often grow beyond this simplistic ideal. Complex workflows may require observability, fault tolerance, and testability woven into the entire flow. Let’s explore what that entails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottlenecks in the Process
&lt;/h2&gt;

&lt;p&gt;There are several critical pressure points where things can go wrong, and it's unwise to treat webhook handling as purely a business logic concern. It belongs in the infrastructure layer. Some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;outgoing request&lt;/strong&gt; might fail due to a missing parameter. Without graceful error handling, the user is stuck.&lt;/li&gt;
&lt;li&gt;At the &lt;strong&gt;application boundary&lt;/strong&gt;, we must enforce static types to reliably intercept data. If not, failure will only surface on the provider’s end, leaving users confused until reconciliation (if any) occurs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload processing&lt;/strong&gt; should happen in a &lt;strong&gt;decoupled business layer&lt;/strong&gt;, enabling graceful failure and testability via mock payloads—all while respecting separation of concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fleshing It Out
&lt;/h2&gt;

&lt;p&gt;Suphle helps handle infrastructure-layer concerns out of the box, offering tools for fail-safes at key points in the request lifecycle.&lt;/p&gt;

&lt;p&gt;Let’s start with &lt;strong&gt;outbound request initiation&lt;/strong&gt;. Suphle provides a base class for such requests: &lt;code&gt;Suphle\IO\Http\BaseHttpRequest&lt;/code&gt;. It wraps outgoing PSR-compliant requests with useful features like error-catching, telemetry reporting, fallback support, and domain-level object mapping (DTOs).&lt;/p&gt;

&lt;p&gt;Here’s a sample request borrowed from the &lt;a href="https://angry-cray-9c191b.netlify.app/docs/v1/http/#writing-request-objects" rel="noopener noreferrer"&gt;official documentation&lt;/a&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Suphle\IO\Http\BaseHttpRequest&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;Psr\Http\Message\ResponseInterface&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;GuzzleHttp\RequestOptions&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;TriggerPayment&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseHttpRequest&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;getRequestUrl&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;"http://some-gateway.com/pay"&lt;/span&gt;&lt;span class="p"&gt;;&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;getHttpResponse&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="kt"&gt;ResponseInterface&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;requestClient&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="s2"&gt;"post"&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;getRequestUrl&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

                &lt;span class="nc"&gt;RequestOptions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

                    &lt;span class="s2"&gt;"SECRET_KEY"&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;envAccessor&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FLUTTER_WAVE_PK"&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="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;convertToDomainObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ResponseInterface&lt;/span&gt; &lt;span class="nv"&gt;$response&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;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// filter to taste or cast to DSL&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then consume this in a coordinator like so:&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;HttpCoordinator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ServiceCoordinator&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;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;TriggerPayment&lt;/span&gt; &lt;span class="nv"&gt;$httpService&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;makePayment&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="kt"&gt;iterable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$dslObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;httpService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDomainObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;httpService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasErrors&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="c1"&gt;// derive $dslObject some other way&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$dslObject&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 is both neat and powerful. With the outbound request sorted, let’s now explore the return leg of the process—handling incoming webhook payloads.&lt;/p&gt;

&lt;p&gt;Suphle provides a specialized request reader, &lt;a href="https://angry-cray-9c191b.netlify.app/docs/v1/service-coordinators/#non-model-based-request-type" rel="noopener noreferrer"&gt;&lt;code&gt;Suphle\Services\Structures\ModellessPayload&lt;/code&gt;&lt;/a&gt;, which allows you to extract known domain objects (DSLs) from payloads. Validation failures at this level are sent to your telemetry service, without impacting the user journey.&lt;/p&gt;

&lt;p&gt;Here’s a practical 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;use&lt;/span&gt; &lt;span class="nc"&gt;Suphle\Services\Structures\ModellessPayload&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;ExtractPaymentFields&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ModellessPayload&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;convertToDomainObject&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;payloadStorage&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data"&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;GenericPaidDSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"trx_id"&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 consumed in a coordinator like so:&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;BaseCoordinator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ServiceCoordinator&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;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;TransactionService&lt;/span&gt; &lt;span class="nv"&gt;$transactionService&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;myCartItems&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;transactionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;modelsToUpdate&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;genericWebhook&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ExtractPaymentFields&lt;/span&gt; &lt;span class="nv"&gt;$payloadReader&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;transactionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateModels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; 
                &lt;span class="nv"&gt;$payloadReader&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDomainObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="cm"&gt;/*returns instance of GenericPaidDSL*/&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;The &lt;code&gt;TransactionService&lt;/code&gt; encapsulates all the safety mechanisms we've introduced so far. When built correctly, extending &lt;code&gt;Suphle\Services\UpdatefulService&lt;/code&gt; and implementing the &lt;code&gt;SystemModelEdit&lt;/code&gt; interface guarantees mutative operations occur within safe, locked transactions—ensuring no two users mutate the same resource concurrently.&lt;/p&gt;

&lt;p&gt;You can read more about these guarantees in the &lt;a href="https://angry-cray-9c191b.netlify.app/docs/v1/service-coordinators/#mutative-database-decorators" rel="noopener noreferrer"&gt;documentation on mutative decorators&lt;/a&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="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Suphle\Services\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;UpdatefulService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Structures\BaseErrorCatcherService&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Suphle\Services\Decorators\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;InterceptsCalls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;VariableDependencies&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Suphle\Contracts\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Services\CallInterceptors\SystemModelEdit&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;Suphle\Events\EmitProxy&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;Illuminate\Support\Collection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[InterceptsCalls(SystemModelEdit::class)]&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;VariableDependencies&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;

    &lt;span class="s2"&gt;"setPayloadStorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"setPlaceholderStorage"&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;TransactionService&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;UpdatefulService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;SystemModelEdit&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;BaseErrorCatcherService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EmitProxy&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;const&lt;/span&gt; &lt;span class="no"&gt;EMPTIED_CART&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cart_empty"&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;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;CartService&lt;/span&gt; &lt;span class="nv"&gt;$cartService&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;readonly&lt;/span&gt; &lt;span class="kt"&gt;TransactionVerifier&lt;/span&gt; &lt;span class="nv"&gt;$verifyPayment&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;Events&lt;/span&gt; &lt;span class="nv"&gt;$eventManager&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;updateModels&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nv"&gt;$genericPaidDSL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$products&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;modelsToUpdate&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;shouldDispenseService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$genericPaidDSL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$products&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$products&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"sold"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&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;emitHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EMPTIED_CART&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$products&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// received by payment, order modules etc&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;cartService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&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;modelsToUpdate&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="kt"&gt;iterable&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;cartService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;authProducts&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;shouldDispenseService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;GenericPaidDSL&lt;/span&gt; &lt;span class="nv"&gt;$genericPaidDSL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt; &lt;span class="nv"&gt;$products&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;verifyPayment&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setTrxId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$genericPaidDSL&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;trxId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// used to fetch request details&lt;/span&gt;

        &lt;span class="nv"&gt;$dslObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;verifyPayment&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDomainObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;verifyPayment&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasErrors&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$dslObject&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;matchAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$products&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Fraudulent Payment"&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;Much of the code above is standard boilerplate with dedicated documentation chapters. We’ve included it here for realism and completeness. What's most relevant to our webhook flow is how naturally &lt;code&gt;GenericPaidDSL&lt;/code&gt; is consumed throughout the layers—without relying on brittle scaffolding or random PHP wiring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Handling Multiple Providers
&lt;/h2&gt;

&lt;p&gt;Suppose your application integrates with multiple payment gateways. You could adjust &lt;code&gt;ExtractPaymentFields&lt;/code&gt; to dynamically map incoming payloads to their appropriate DSLs:&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;Suphle\Services\Structures\ModellessPayload&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;ExtractPaymentFields&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ModellessPayload&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;convertToDomainObject&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;payloadStorage&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data"&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;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"provider_indicator"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"paystack"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaystackDSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"trx_id"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FlutterwaveDSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"trx_id"&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;Alternatively, you could delegate this responsibility to Suphle’s &lt;a href="https://angry-cray-9c191b.netlify.app/docs/v1/service-coordinators/#defining-condition-factories" rel="noopener noreferrer"&gt;&lt;code&gt;Condition factories&lt;/code&gt;&lt;/a&gt; for even tighter encapsulation—ideal when DSLs require container hydration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Testing is integral to engineering confidence in your implementation. Fortunately, Suphle’s architecture makes this easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can &lt;strong&gt;skip all &lt;code&gt;BaseHttpRequest&lt;/code&gt; subclasses&lt;/strong&gt; and inject DSLs directly into domain classes.&lt;/li&gt;
&lt;li&gt;Optionally test &lt;code&gt;ModellessPayload&lt;/code&gt; subclasses if they contain complex conditionals.&lt;/li&gt;
&lt;li&gt;Focus most of your testing efforts on &lt;code&gt;TransactionService::updateModels&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s how a test could look:&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="nn"&gt;AllModules\CartModule\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Meta\CartModuleDescriptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Services\TransactionService&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;AppModels\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;CartProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;EloquentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Suphle\Testing\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;TestTypes\ModuleLevelTest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Proxies\WriteOnlyContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Condiments\EmittedEventsCatcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Condiments\BaseDatabasePopulator&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;TransactionTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ModuleLevelTest&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;BaseDatabasePopulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EmittedEventsCatcher&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;getActiveEntity&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;EloquentUser&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="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getModules&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&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;replicateModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CartModuleDescriptor&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="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;WriteOnlyContainer&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;)&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;replaceWithMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransactionService&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="nc"&gt;TransactionService&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="s2"&gt;"shouldDispenseService"&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="c1"&gt;// or simulate failure if you wish: $this-&amp;gt;throwException(InsufficientBalance::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="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;test_can_update_models&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// given&lt;/span&gt;
        &lt;span class="nv"&gt;$genericPaidDSL&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;GenericPaidDSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"nmeri"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;replicator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;modifyInsertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$builder&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;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nc"&gt;CartProducts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&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;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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="mi"&gt;0&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;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;products&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;getContainer&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;getClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransactionService&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateModels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$genericPaidDSL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// when&lt;/span&gt;

        &lt;span class="c1"&gt;// then&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;assertHandledEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;TransactionService&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="nc"&gt;TransactionService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EMPTIED_CART&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$product&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;databaseApi&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertDatabaseHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"sold"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reconciliation Job: A Final Safety Net
&lt;/h2&gt;

&lt;p&gt;Even with our rigorous setup—outbound request wrappers, typed payload interceptors, and transactional guarantees—there may be rare occasions where failures escape our immediate logic. For example, a payment provider might confirm a transaction we never received due to a brief network outage or delayed webhook delivery.&lt;/p&gt;

&lt;p&gt;To cover such cases, we schedule a cron job that queries the provider's API for transactions in a given timeframe, compares them against our own database records, and reconciles any mismatch. For semantic purposes, you might decide to implement the &lt;a href="https://angry-cray-9c191b.netlify.app/docs/v1/queues/#writing-task-classes" rel="noopener noreferrer"&gt;&lt;code&gt;Suphle\Contracts\Queues\Task&lt;/code&gt;&lt;/a&gt; interface.&lt;/p&gt;

&lt;p&gt;A hypothetical implementation may 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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Suphle\Contracts\Queues\Task&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;RectifyFailedPaystack&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Task&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;protected&lt;/span&gt; &lt;span class="kt"&gt;TransactionService&lt;/span&gt; &lt;span class="nv"&gt;$transactionService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;UserService&lt;/span&gt; &lt;span class="nv"&gt;$userService&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;handle&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;$failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;transactionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fetchPaystackTransfers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// this would implement some outbound call for fetching transactions acknowledged on payment provider side, mapped to our GenericPaidDSL&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&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;PaystackDSL&lt;/span&gt; &lt;span class="nv"&gt;$paystackDSL&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="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;transactionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paystackHasRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$paystackDSL&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;trxId&lt;/span&gt;&lt;span class="p"&gt;);&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;each&lt;/span&gt;&lt;span class="p"&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;PaystackDSL&lt;/span&gt; &lt;span class="nv"&gt;$paystackDSL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$paystackDSL&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&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;transactionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addUserForPaystack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$paystackDSL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We avoid using &lt;code&gt;TransactionService::updateModels&lt;/code&gt; here since &lt;code&gt;cartService::authProducts&lt;/code&gt; relies on authenticated user, while this code runs in a cron context devoid of users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we walked through building and consuming webhooks in a watertight, testable way—leveraging Suphle’s rich features to handle common pitfalls with elegance and minimal boilerplate. From outbound request handling to payload parsing, from DSL consistency to transactional safety, Suphle has your back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clarification for Skeptical Readers
&lt;/h3&gt;

&lt;p&gt;Although this article may appear sparse in implementation details, every major component is in fact fully functional and directly usable in a live Suphle application. The reason it seems lightweight is because Suphle handles a substantial amount of heavy lifting behind the scenes—error reporting, transactional safety, dependency injection, typed requests—all while keeping your business logic clean and testable.&lt;/p&gt;

&lt;p&gt;Certain essentials like routing setup and module creation (Suphle supports modular monoliths out of the box) have been omitted here, not due to incompleteness, but because they aren’t central to webhook handling and are thoroughly documented in the official docs. What you see here is exactly how a production-grade Suphle app would appear, minus domain-specific logic like UI layers or custom billing flows.&lt;/p&gt;

&lt;p&gt;If you found this helpful, please consider sharing the article and dropping a &lt;a href="https://github.com/nmeri17/suphle" rel="noopener noreferrer"&gt;star on GitHub&lt;/a&gt;. Cheers!&lt;/p&gt;

</description>
      <category>php</category>
      <category>tutorial</category>
      <category>testing</category>
    </item>
    <item>
      <title>A synopsis of the Suphle Framework</title>
      <dc:creator>O ji nwayo e je</dc:creator>
      <pubDate>Wed, 26 Apr 2023 18:36:37 +0000</pubDate>
      <link>https://forem.com/mmayboy_/a-synopsis-of-the-suphle-framework-5hb9</link>
      <guid>https://forem.com/mmayboy_/a-synopsis-of-the-suphle-framework-5hb9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello there. In this post we will be demonstrating some functionalities of the &lt;a href="https://suphle.com"&gt;Suphle framework&lt;/a&gt; by drawing parallels between the objectives constituting its blueprint, and implementations intended to alleviate code smells that hamper project maintainability. Suphle is a new PHP framework released recently, for building sophisticated full-stack software. Suphle is most suitable for business-critical database-driven applications that will be actively maintained over time.&lt;/p&gt;

&lt;p&gt;For readers not abreast with PHP's development in recent years, permit me to dispel the following misconceptions regarding the language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;With the aid of a load-balancer and no extensions, the PHP processes created by the Suphle server are no longer destroyed after handling each request. Application instances are re-used just as those built in other server-side languages. The project responsible for this is called Roadrunner. It equally grants us access to functionality previously absent in the language e.g. Websockets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suphle bundles with Psalm, a library for detecting and correcting type-related errors at compile/server-build time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PHP is not synonymous with SQL injections and procedural functions. Majority of Suphle's functionality dwells in instantiable classes. There are zero procedural-style functions in Suphle. These classes are auto-wired for you by Suphle's Container. A plethora of ORMs exist to discourage you from exposure to SQL injection risks by minimizing direct interaction with queries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suphle's command for &lt;a href="https://suphle.com/docs/v1/routing#Generating-CRUD-defaults"&gt;bootstrapping resource management&lt;/a&gt; is not marketed as a revolutionary facility, since such convenience was already possible more than a decade ago when DHH presented Rails to the world. The industry has not stagnated in that time; as such, new tools are expected to improve on that model. Despite domain uniqueness from business to business, some patterns have emerged as non-negotiable in the journey of the web's evolution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An API&lt;/li&gt;
&lt;li&gt;Fidelity on one or more client interfaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Various technologies have struggled to assert their presence in every user-facing software published. From serious ones like real-time communication, to trivial ones like GDPR dialogs. Developers within this epoch start out practicing fundamentals such as retrieving input values, exchanging them for database objects, formatting them into shapes expected by the consumer. Unfortunately for them, these rudimentary etiquettes turn out grossly inadequate under real life work conditions. Most enterprise frameworks will arm its developer with the constructs for meeting these needs, but leave their usage to the developer's discretion. This is perhaps Suphle's most significant difference from them -- it's like a code review that complains about anything whose absence is dangerous.&lt;/p&gt;

&lt;p&gt;In addition to this core drive, Suphle has the following over-arching objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Breaking business logic into silos for better management between multiple maintainers. A non-existent architecture is potentially a haphazard one, making it difficult for newer collaborators to maintain the project in the absence of the original author.&lt;/li&gt;
&lt;li&gt;Enforcing recommended code patterns that encourage cohesion, DRY, testability.&lt;/li&gt;
&lt;li&gt;Minimizing application failure to the barest minimum.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on both my experience and extensive research, it is evident that certain practices are indispensable for applications that fall into the niche described earlier. There are &lt;a href="https://suphle.com"&gt;dedicated chapters on the documentation&lt;/a&gt; that go into detail regarding the solutions Suphle offers for circumventing undesirable circumstances. However, diving into the tome has reportedly been overwhelming. So in this post, we will skim over an overview of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business logic architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Modules
&lt;/h3&gt;

&lt;p&gt;Perhaps the most visible update in the structure of applications built by immigrating developers is that Suphle brings native support of the tried and proven &lt;a href="https://suphle.com/docs/v1/modules"&gt;Modular-monolith concept to PHP Frameworks&lt;/a&gt;. It's a progression from code bases with a &lt;code&gt;Controllers&lt;/code&gt; folder full of Controllers catering to diverse resources. Aside the potential of relevant modules being scaled into their own microservices, this structure is most advantageous in scenarios where resources entail more than basic CRUD queries. Resources that grow to this size, or those important enough to the business, may necessitate limited interference by assigning one maintainer to it, whose development and testing is designated to an environment as close as possible to isolation (even when they need to depend or communicate with each other).&lt;/p&gt;

&lt;p&gt;Below, a Suphle application's entry point is shown to expose two modules for HTTP request handling:&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;AllModules&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;Suphle\Modules\ModuleHandlerIdentifier&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;Suphle\Hydration\Container&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;AllModules\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;ModuleOne\Meta\ModuleOneDescriptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ModuleTwo\Meta\ModuleTwoDescriptor&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;PublishedModules&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ModuleHandlerIdentifier&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;getModules&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&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;ModuleOneDescriptor&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;Container&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;ModuleTwoDescriptor&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;Container&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 is your application's outskirts, superceding the intra-module route collections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforced dependency prohibition
&lt;/h3&gt;

&lt;p&gt;For its high flexibility, Suphle has this as one of its most opinionated aspects. Developers fall for the temptation to perform all sorts of operations within their Controllers, at the detriment of future maintainability. For this reason, Suphle's controllers are sanitized during the &lt;a href="https://suphle.com/docs/v1/application-server#Dependency-checks"&gt;application's build phase&lt;/a&gt;. Detected violations will halt server build. Following this divergence from the traditional motive of controllers, it makes sense to redefine them into what we call &lt;a href="https://suphle.com/docs/v1/Service-coordinators"&gt;Service-coordinators&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mandatory integrations
&lt;/h2&gt;

&lt;p&gt;Both frameworks and their accompanying libraries make provision for bolstering their programs with recommended integrations. Unfortunately, these aren't apparent or glaring to all users of the framework. These integrations cut across components of back-end engineering ranging from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Not centralizing things, to,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leaving them for later -- one of the technical debts that never gets paid down the line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Erasing type-related errors.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In concrete terms, the following components are affected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/Image-upload"&gt;Image upload&lt;/a&gt;: The object for interacting with uploaded images is designed in such a way that further evaluation of the request fails if the image is not stored without further optimization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/Service-coordinators#Validating-incoming-requests"&gt;Request validation&lt;/a&gt;: As an incentive for all validatable endpoints to be decorated by a set of rules, the Framework will terminate request handling if such endpoints are missing validation rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/application-server#Static-type-checks"&gt;Static-type errors&lt;/a&gt;: Psalm is bundled with the server build command in order to either indicate or correct type-related errors in a dynamically-typed language; thereby elevating PHP to a sphere previously occupied by only TypeScript.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/Service-coordinators#Mutative-database-decorators"&gt;Database transactions&lt;/a&gt;: Endpoints mutating the database in a non-trivial way i.e. executing multiple disparate queries should be wrapped by a transaction. Suphle provides decorators that abstract away the low-level details such as appropriate lock strategy, error management, protecting update integrity, etc, across one or more modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/database#Configuring-table-structure"&gt;Data modelling&lt;/a&gt;: Modifications to the connected ORM force all inheriting models to be composed of migrations and factories, encouraging seeding through tests rather than a DBMS.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Violations to these rules are expected to be caught during development/testing, once the routes are being visited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-explanatory design
&lt;/h2&gt;

&lt;p&gt;These are semantics geared towards constructing software that is better open for not only development co-location, but more robust implementation of business requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/authorization"&gt;Authorization&lt;/a&gt;: Model-based authorizers promotes authorization-based business rules from the scope of services, into the over-arching model layer itself; leaving the services to handle other computations without fear of manual adherence to those rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/Service-coordinators#Condition-factories"&gt;Conditional factory&lt;/a&gt;: This a construct for pooling conditional business rules into an OOP umbrella, abstracting it away for reuse and testability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/Service-coordinators#Retrieving-request-input"&gt;Input reader&lt;/a&gt;: This builds upon the existing convention of hydrating ORM instances out of route pattern placeholders, both granting greater flexibility in query customization to the caller and restricting database fields retrieved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/events"&gt;Events&lt;/a&gt;: Suphle is emphatic in its recommendation to use events, narrowly avoiding an enforcement of their usage. Their binding API is designed to only tolerate assignment of all handlers of events from an emitter to one listening class. It also makes special provision for listening to events emitted by neighboring modules.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A realistic event bind and emitting flow is shown below:&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;Suphle\Events\EventManager&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;AssignListeners&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;EventManager&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;registerListeners&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="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CheckoutCart&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="nc"&gt;CartReactor&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CheckoutCart&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EMPTIED_CART&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"handleEmptied"&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// triggers CartReactor-&amp;gt;handleEmptied with any payload sent below&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="c1"&gt;#[InterceptsCalls(SystemModelEdit::class)]&lt;/span&gt;
&lt;span class="c1"&gt;#[VariableDependencies([&lt;/span&gt;

    &lt;span class="s2"&gt;"setPayloadStorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"setPlaceholderStorage"&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;CheckoutCart&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;UpdatefulService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;SystemModelEdit&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;BaseErrorCatcherService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EmitProxy&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;const&lt;/span&gt; &lt;span class="no"&gt;EMPTIED_CART&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cart_empty"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Events&lt;/span&gt; &lt;span class="nv"&gt;$eventManager&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;updateModels&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;cartBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;products&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;decrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"quantity"&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;emitHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EMPTIED_CART&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;cartBuilder&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;cartBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&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;ul&gt;
&lt;li&gt;
&lt;a href="https://suphle.com/docs/v1/http"&gt;Outbound requests&lt;/a&gt;: Abstract HTTP requests out of the business and services department such that response can be statically represented to the caller, tested, and consumed safe from errors. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Infallible requests
&lt;/h2&gt;

&lt;p&gt;Automated testing is one of software's most important facets. This is why each chapter of the documentation is punctuated with a section describing how to test the component it discusses, multiple chapters of the &lt;a href="https://suphle.com/docs/v1/Appendix"&gt;Appendix&lt;/a&gt; are dedicated to the subject, in addition to the main &lt;a href="https://suphle.com/docs/v1/Testing"&gt;Testing chapter&lt;/a&gt;. In spite of these, it's inevitable for requests to fail in production. When that occurs, instead of jeopardizing the entire request, writing exception details to a log and waiting for the developer to attend to it, &lt;a href="https://suphle.com/docs/v1/Service-coordinators#Auto-service-error-handling"&gt;Suphle's services are engineered&lt;/a&gt; to not only isolate points of failure, but, with the aid of &lt;a href="https://suphle.com/docs/v1/exceptions#Broadcaster-adapters"&gt;a Bugsnag configuration&lt;/a&gt;, report the emergency through a queued job.&lt;/p&gt;

&lt;p&gt;Services insured by this wrapper merely need one decorator to activate it, while the caller now has to check the status of the object before consuming its result.&lt;/p&gt;

&lt;p&gt;Suppose we have some Coordinator fetching data from the sources &lt;code&gt;service1Result&lt;/code&gt; and &lt;code&gt;service2Result&lt;/code&gt; in order to complete its request, a volatile &lt;code&gt;service2Result&lt;/code&gt; can be reliably executed as follows:&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;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"service1Result"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$service2Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;throwableService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;throwableService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;matchesErrorMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"getValue"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nv"&gt;$service2Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;otherSource&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;alternateValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// perform some valid action&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"service2Result"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$service2Result&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;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even without the check &lt;code&gt;$this-&amp;gt;throwableService-&amp;gt;matchesErrorMethod("getValue")&lt;/code&gt;, a failure of the preceding statement will not disrupt the successful response nodes, as long as they are independent of each other.&lt;/p&gt;

&lt;p&gt;As you may have guessed, this is most useful to SSR applications where content is fetched and rendered in one go rather than tiny requests to diverse endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some dessert to go with your Suphle
&lt;/h2&gt;

&lt;p&gt;We have discussed features whose usage the Framework vehemently considers critical. In addition to them, Suphle introduces new constructs you are unlikely to have seen anywhere else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/flows"&gt;Flows&lt;/a&gt;: This is an experimental performance booster for optimistically loading various kinds of responses before they are being made. Front-end frameworks already do this but no back-end framework currently preserves outgoing response, extracts nodes out of it, visits them using developer-defined rules, caches the result, serves and invalidates the cache with the intricacies relating to resource originator.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/bridges"&gt;Bridge&lt;/a&gt;: This is intended to facilitate onboarding existing web-based projects written in other PHP frameworks, instead of recreating them from scratch in Suphle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://suphle.com/docs/v1/routing"&gt;Route collection utilities&lt;/a&gt;: Aside their traditional responsibility of translating incoming requests into complementary Coordinators, Suphle's route collection brings numerous infra-level equipments to the doorstep of the average developer. First-class support for versioned routes is &lt;a href="https://github.com/nmeri17/suphle/issues/24"&gt;currently&lt;/a&gt; optional.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An API can be &lt;a href="https://suphle.com/docs/v1/routing#Route-mirroring"&gt;derived from the existing browser&lt;/a&gt; route collections. The browser routes default to your API's &lt;code&gt;v1&lt;/code&gt;, but can also be overridden by more specific API patterns. If this sounds familiar to the concept of Content-Negotiation, it's because they are remotely similar. However, Route-mirroring differs ever so slightly: Content-Negotiation is often implemented as a middleware that simply replaces response format. It's inadequate for applications that require entirely different content on specific paths, for example between the mobile and web versions. &lt;/p&gt;

&lt;p&gt;Suphle's route collections also bring closer the concept of &lt;a href="https://suphle.com/docs/v1/routing#Feature-toggling"&gt;canary requests&lt;/a&gt; for A/B, internal, and temporary feature releases. Canary collections are like custom route collections for defining any conditions for determining what actual route collection the Router should use in serving the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching up with the web's evolution
&lt;/h2&gt;

&lt;p&gt;So, where does Suphle stand within the current development standards? DHH has repeatedly proven to be a futurist ahead of his time. One of his great gifts to the development world is &lt;a href="//turbo.hotwire.dev"&gt;a handy library&lt;/a&gt; that brings fidelity to server-rendered software, for a fraction of the complexity or replicating that using front-end frameworks.&lt;/p&gt;

&lt;p&gt;Suphle leverages these libraries for &lt;a href="https://suphle.com/docs/v1/templating"&gt;its presentation layer&lt;/a&gt;, entirely obviating the need for an API or dedicated front-end developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Some have argued that not all applications require this level of meticulousness, and that is correct. The sort of applications that derive little to no benefit from the optimizations and introductions discussed here, the framework will be an overkill. It's neither something to be ashamed of or hate the Framework for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5OcGocSs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kf7cm4xcsb2egjgssxy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5OcGocSs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kf7cm4xcsb2egjgssxy.jpg" alt="cup-of-tea" width="554" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, do beware of prevention being better than cure. An anecdote to buttress this admonition may suffice: An important document I was writing recently, got overwritten when I unwittingly edited it in-between my devices. All updates made on my laptop were lost when the phone resumed where I left off and synced with the server by sending its present contents.&lt;/p&gt;

&lt;p&gt;Since it's neither a collaborative or version controlled program, syncing local copies kind of makes sense, under the assumption that changes may have been made while device was offline. Suphle on the other hand, averts this tragedy using one of the decorators discussed earlier, specifically, &lt;code&gt;MultiUserModelEdit&lt;/code&gt;, discussed at length in &lt;a href="https://suphle.com/docs/v1/Service-coordinators#User-induced-updates"&gt;its appropriate section&lt;/a&gt;. Ideally, the user should have been informed of a newer version by terminating the synchronization, regardless of whether it's a collaborative or version controlled software. Thus, it may not be entirely wise to wait until you find a use-case for all components mentioned here.&lt;/p&gt;

&lt;p&gt;Most applications are essentially CRUD, but even among them, there are those whose endpoints hardly make simplistic queries. They entail computation of, sometimes, frequently changing business rules. A lot of thought ought to go into their implementation instead of simply going with the flow. Applications that fall into this bracket may find integrating Suphle's implementations painstaking. However, Suphle's mission is standing in as a disciplinarian, not to make the developer's life more difficult. Its utilities should enlighten and equip us with conventions and adequate tooling for improving the quality of our programs.&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>backend</category>
    </item>
    <item>
      <title>Introducing Suphle - The tale of a modern PHP framework</title>
      <dc:creator>O ji nwayo e je</dc:creator>
      <pubDate>Sat, 24 Dec 2022 20:30:51 +0000</pubDate>
      <link>https://forem.com/mmayboy_/introducing-suphle-the-tale-of-a-modern-php-framework-54i9</link>
      <guid>https://forem.com/mmayboy_/introducing-suphle-the-tale-of-a-modern-php-framework-54i9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nmeri17/suphle/"&gt;Suphle&lt;/a&gt; is an opinionated PHP framework for enterprises and SAASes to build anything from command line applications, to robust APIs, to server rendered web apps, without compromising on the high fidelity of SPAs. Because the line between the last two in Suphle is nonexistent, you can virtually go from creating the back end of a web app to having a versioned, documented API in a heartbeat. Aside that, it guarantees your app will never break until you want it to; be it after product release or subsequent maintenance. Then, it has first class support for modular monoliths that can either be exhumed into their own micro-service or used as template for extending modules to reuse.&lt;/p&gt;

&lt;p&gt;Suphle is anything but a jamboree for the author to understand how routing requests to controllers work. Its intended audience is enterprises with a fleet of developers working hands-on to create solutions to positively impact users relying on its efficiency. It starts to shine during rapid application development or in environments experiencing feature creep&lt;/p&gt;

&lt;p&gt;Whichever is the case, your resulting program should not compromise on being an architectural masterpiece – an art form in itself&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Copy that. Tbf the overview sound great, but does it contain what my current framework does?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What are the regular culprits you expect at any serious back end framework? Routing, container, authentication, authorisation, events, DAL, testing, middleware, controllers. The rest are nice to haves: task scheduling, sessions, http, cache, templating, validation, exception handling, CLI. Remind me if I missed anything.&lt;br&gt;
Suphle has all that, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Bold statement. What could possibly be more than those?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Components themselves arguably don't make a framework. That is why indie developers can get away with cobbling random libraries that fit their needs together instead of starting with the gigantic household names. The first version of Suphle handled middleware, authentication and routing in one class &amp;lt;=500 LOC. It was functionally, a framework that demonstrated the possibility of not formalizing components.&lt;/p&gt;

&lt;p&gt;What has been done differently this time around is to &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Formalize the components to better accommodate their now larger capacity and recognise diverging responsibilities. &lt;/li&gt;
&lt;li&gt;More importantly, to take the spotlight away from components in and of themselves, and instead, point it onto the larger picture. This involves the following expectations:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;1) Domain maintenance as perceived by business department&lt;br&gt;
2) Seamless end user experience&lt;br&gt;
3) Testability&lt;br&gt;
4) Project evolution without strict reliance on:&lt;/p&gt;

&lt;p&gt;i) Collaboration guidelines or&lt;br&gt;
ii) Developer competence&lt;/p&gt;

&lt;p&gt;5) Better managed errors&lt;/p&gt;

&lt;p&gt;One of the factors that best exemplifies insignificance of components is that majority of Suphle is democratised by being composed of contracts. There is no little to no vendor lock-in, as long as your component of choice fulfills expected contract&lt;/p&gt;

&lt;p&gt;The main takeaway here is to repurpose those known components toward these goals or create new ones when the existing ones can be improved upon. While frameworks number in the dozen, core libraries in even greater abundance, Suphle can be thought to take things a step further to bridge the gap left behind. Below, we look at real life examples of the bullet points outlined above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal feature release and archiving features&lt;/li&gt;
&lt;li&gt;Breaking unchanged parts of the code by modifying dependencies cuz No clearcut dependency chain, and certainly, no integration tests&lt;/li&gt;
&lt;li&gt;Requiring a full stack developer to work on our UIs (before they were ported to SPAs) &lt;/li&gt;
&lt;li&gt;Entire pages crashing because of an error affecting an insignificant page segment/data node&lt;/li&gt;
&lt;li&gt;Waiting for negative customer feedback before knowing something went wrong, then wrangling error logs&lt;/li&gt;
&lt;li&gt;Sacrificing man hours after giving up on SSR. A front end dev was hired. The back end had to be duplicated into an API with slightly diverging functionality.&lt;/li&gt;
&lt;li&gt;Chasing and duplicating state and errors between the SPA and back end, for the sole purpose of a SPA-ey feel/fidelity&lt;/li&gt;
&lt;li&gt;Cluelessness when our callback URLs broke in transit&lt;/li&gt;
&lt;li&gt;API documentation, testing, breaking clients thanks to indiscriminate updates since there was no versioning&lt;/li&gt;
&lt;li&gt;Irresponsible practices such as requests without validators, fetching all database columns, improper or no model authorisation, dumping whatever we had nowhere else to put in middlewares, gigantic images ending up at the server, models without factories, stray entities floating about when their owner model gets deleted, not guarding negative integers from sneaking in from user input, I could go on&lt;/li&gt;
&lt;li&gt;Corrupted data when operations separated by logic, that should've been inserted together gets broken in between&lt;/li&gt;
&lt;li&gt;Gnarly merge conflicts among just a handful contributors &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you or your team are struggling with any of these, you're in luck! They are some of the problems Suphle was built to solve&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Haha! I am howling right now. Tbh, giving it a try is really tempting at this point, but I'm a little sceptical given its young age and obvious lack of libraries. Might check back, later?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A_2ny-ht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrxzn0p3s9kcd174kcfk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A_2ny-ht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrxzn0p3s9kcd174kcfk.jpg" alt="Judge me by my size" width="798" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, it's brand new, but it doesn't lack any more libraries than laravel. The bridge component has adapters well defined to encourage development of plugins for other frameworks. But currently, the laravel adapter enables you access your laravel service providers, container bindings, routes, config, helpers, migrations, in Suphle without the friction that should've come from combining two drastically contrasting frameworks. Mind you, each of them is active on an opt-in basis. Suphle doesn't operate an exclusive us vs them model. It's an inclusive us AND them. Don't throw away your existing apps or port them. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Are you kidding me?!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Thankfully, not. Matter of fact, There's an appendix in the docs that go into detail about the ins and outs of automating your tests, especially intended for those who don't know how to, tried and failed to wrap their heads /workflow around TDD, or who don't really think testing is important – that demographic of developers. It even includes articles for progressive development and inclusion of new features, replacing old ones etc.&lt;/p&gt;

&lt;p&gt;"..."&lt;/p&gt;

&lt;p&gt;"..."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4rZYwHG6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5asaxy481ls0p4lav1q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4rZYwHG6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5asaxy481ls0p4lav1q.gif" alt="Shut up and take my money" width="500" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ha! I think you mean "take all my existing and future codebases". As of the moment, the documentation is still in the works, I'll say &amp;gt;80%. Docs repo can be cloned to get a feel of what's going on. The tests can equally be used to fill missing gaps. No further modification will be made to the APIs until the official release. It'll be announced here but you can also watch the repo.&lt;/p&gt;

&lt;p&gt;If you're interested in contributing, one feature that won't accompany the first release although I absolutely wish it did, is the &lt;a href="https://github.com/nmeri17/suphle/issues/24"&gt;routing aggregator&lt;/a&gt;. It's the one part of the blueprint discussed above that is yet to be implemented.&lt;/p&gt;

&lt;p&gt;To show support, kindly consider leaving a star on the &lt;a href="https://github.com/nmeri17/suphle"&gt;repo&lt;/a&gt;. Many thanks for reading&lt;/p&gt;

</description>
      <category>php</category>
      <category>framework</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
