<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Mabadeje</title>
    <description>The latest articles on DEV Community by Daniel Mabadeje (@danielmabadeje).</description>
    <link>https://dev.to/danielmabadeje</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F507245%2Fec4bd258-a00f-455c-8c3b-33da45b0aca5.jpeg</url>
      <title>DEV Community: Daniel Mabadeje</title>
      <link>https://dev.to/danielmabadeje</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielmabadeje"/>
    <language>en</language>
    <item>
      <title>What a Failing Test Suite Taught Me About a Silent Laravel Security Gap</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:28:08 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/what-a-failing-test-suite-taught-me-about-a-silent-laravel-security-gap-16il</link>
      <guid>https://dev.to/danielmabadeje/what-a-failing-test-suite-taught-me-about-a-silent-laravel-security-gap-16il</guid>
      <description>&lt;p&gt;This is more like a heads-up article and a little mortification that I didn't realize this quickly…&lt;br&gt;
Just a little… 😂&lt;/p&gt;

&lt;p&gt;I recently spent an evening working through a batch of failing tests in a Laravel API. The failures looked unrelated on the surface. One endpoint was throwing a cryptic cURL error, another was silently accepting data it should have rejected, and a third was returning the wrong HTTP status code entirely. First of all, that was weird… It's an anti-pattern to test a third-party endpoint. So… why is my test calling an endpoint when I have set mocks???&lt;/p&gt;

&lt;p&gt;Different endpoints, different symptoms. Same one-line root cause, every single time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug, in its natural habitat&lt;/strong&gt;&lt;br&gt;
Here’s a simplified version of what I kept finding:&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;VerificationController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;verifyBvn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;verificationService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&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;Nothing here looks wrong. It compiles. It runs. In the happy path, where the client sends well-formed data, it works exactly as expected.&lt;/p&gt;

&lt;p&gt;The problem only shows up when someone sends bad data, a BVN that’s 3 characters instead of 11, an amount that’s negative, or a required field that’s just missing. Because nothing in this controller ever validates the input, that bad data doesn’t stop here. It flows straight into the service layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="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;bvnProvider&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;startVerification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bvn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// whatever the client sent, unchecked&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And straight out to wherever that service layer talks to next. In my case, a real external API call, which then failed with a Guzzle exception that had nothing obviously to do with “the BVN was too short.” A validation problem disguised itself as an infrastructure problem, several layers downstream from where it should have been caught.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix looks almost too small.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VerifyBvnRequest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FormRequest&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;authorize&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rules&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="s1"&gt;'bvn'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'digits:11'&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;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;VerificationController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;verifyBvn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;VerifyBvnRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;verificationService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&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;That’s it. One class, one changed type-hint. No new logic in the controller, no new logic in the service. And yet this one change is the entire fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this isn’t just cosmetic and what Laravel actually does&lt;/strong&gt;&lt;br&gt;
It’s easy to see Request vs. SomeCustomRequest as a style preference... maybe even a lazy shortcut when you're prototyping and don't want to bother writing rules yet. But in Laravel, the type hint on a controller method parameter isn't just for documentation sake. It's an instruction to the service container about what to construct and how.&lt;/p&gt;

&lt;p&gt;Jeffrey Way put it best on one of his Laracasts years ago—"The code in your controller method literally never executes unless the request passes validation first."&lt;/p&gt;

&lt;p&gt;I am probably paraphrasing it, but yeah 😂.&lt;/p&gt;

&lt;p&gt;When Laravel sees a parameter type-hinted as a class extending FormRequest, it doesn't just hand your controller the raw request. It:&lt;/p&gt;

&lt;p&gt;Instantiates your FormRequest class through the container.&lt;br&gt;
Calls authorize() . If it returns false, the request is rejected with a 403 before your controller ever runs.&lt;br&gt;
Runs the input against rules().&lt;br&gt;
If validation fails, it throws a ValidationException immediately. Laravel's exception handler turns that into a 422 JSON response automatically. Your controller method body never executes.&lt;br&gt;
This happens because FormRequest implements Illuminate\Contracts\Validation\ValidatesWhenResolved, and Laravel's container checks for that interface as part of resolving the object—validation is wired into object construction itself, not into something you have to remember to call inside your method.&lt;/p&gt;

&lt;p&gt;Type-hint a bare Illuminate\Http\Request instead, and none of that machinery exists. Laravel just hands you the request as-is. There's no gate. Whatever arrived over the wire is what your code operates on, unfiltered, all the way down the call stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I’m calling it a security gate, not just a bug&lt;/strong&gt;&lt;br&gt;
It’s tempting to file this under “validation bug” and move on. I’d push back on that framing a little.&lt;/p&gt;

&lt;p&gt;A missing FormRequest doesn't just mean “bad UX” (a confusing 500 instead of a clean 422). It means the boundary between untrusted input and your business logic doesn't exist for that endpoint. Every layer downstream—services, external API calls, database writes—is implicitly trusting that validation already happened. When it didn't, that trust is misplaced, and the failure mode is whatever the least forgiving layer in the chain happens to do with garbage input.&lt;/p&gt;

&lt;p&gt;In a low-stakes case, that’s a bad error message. In a codebase moving money, verifying identity, or writing to a ledger, “the input was never actually checked” is a much bigger sentence than it sounds.&lt;/p&gt;

&lt;p&gt;How I’d catch this faster next time&lt;br&gt;
The good news: this is very greppable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s1"&gt;'(Request \$request)'&lt;/span&gt; app/Http/Controllers modules &lt;span class="c"&gt;#if you use modules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every match is worth five seconds of attention. Some will be legitimately fine … a GET endpoint with nothing to validate. But on any POST/PUT/PATCH method, a bare Request type-hint is a flag worth chasing down, not a style nit to skip past.&lt;/p&gt;

&lt;p&gt;The tests didn’t tell me why things were broken, but they told me where. But the pattern, once I saw it in one place, was exactly the same in four or five others across a codebase I hadn’t fully mapped yet. That’s usually a good sign you’ve found something systemic rather than a one-off typo.&lt;/p&gt;

&lt;p&gt;Oh, and the mock issue from the top? Turned out the mock was working exactly as intended the whole time. 😂 I’d just misattributed a failure to the wrong test method a few debugging rounds earlier. Dont underestimate how much time you can lose staring at the right error message for the wrong test.😂&lt;/p&gt;

&lt;p&gt;I’m done ranting, I think. 😁&lt;/p&gt;

&lt;p&gt;You can connect with me on Twitter: &lt;a href="https://twitter.com/mabadejedanphp" rel="noopener noreferrer"&gt;https://twitter.com/mabadejedanphp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>laravel</category>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>Deploying your Laravel app to AWS from GitHub—A cost-effective way</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:16:00 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/deploying-your-laravel-app-to-aws-from-github-a-cost-effective-way-4f5h</link>
      <guid>https://dev.to/danielmabadeje/deploying-your-laravel-app-to-aws-from-github-a-cost-effective-way-4f5h</guid>
      <description>&lt;p&gt;Okay… where do I start…&lt;br&gt;
I have been kind of running away from AWS for a while now. Not because I hate it; I just felt it had so much going on, and I usually find myself involved more in building applications than setting up cloud infrastructures (until recently). So I thought, as I am practically getting my hands dirty, why not also walk you through what I am doing myself? And yes, also have something as a reference too. 😀&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this guide exist?&lt;/strong&gt;&lt;br&gt;
First, this is basically to show you my thought process of how I manage monolithic applications with a small team, especially for staging environments.&lt;/p&gt;

&lt;p&gt;And also... most “deploy Laravel to AWS” tutorials show you a happy path. I feel like they don’t really tell you about the silent billing plan block that kills your EC2 launch, the Nginx document root trap, the security group CIDR gotcha that times out your database, or the PHP version mismatch that breaks Composer mid-deploy.&lt;/p&gt;

&lt;p&gt;Oh yes, and this goated tweet here on a lighter note. 😂&lt;/p&gt;

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

&lt;p&gt;One more meme picture I saw on my good friend Elisha Ukpong’s status. 😂&lt;/p&gt;

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

&lt;p&gt;At the end of this guide, you should have the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Laravel app deployed to AWS Elastic Beanstalk&lt;/li&gt;
&lt;li&gt;A GitHub Actions CI/CD pipeline using OIDC (no static AWS keys stored in GitHub)&lt;/li&gt;
&lt;li&gt;A managed RDS MySQL database&lt;/li&gt;
&lt;li&gt;Automatic migrations on every deploy&lt;/li&gt;
&lt;li&gt;SSH access for debugging
And an estimated monthly cost of ~$15–25/mo (t2.micro EB instance + db.t4g.micro RDS), compared to ~$60–70/mo for an ECS Fargate + ALB + NAT setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What do I need to continue this guide?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;a href="https://aws.amazon.com/account/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;A Laravel app on GitHub (we’ll make reference to a dev branch for staging)&lt;/li&gt;
&lt;li&gt;A domain (we’ll connect it at the end)&lt;/li&gt;
&lt;li&gt;AWS CLI and EB CLI installed locally
If you don’t have the AWS CLI and EB CLI installed locally for Mac, you can type the command:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;awscli
brew &lt;span class="nb"&gt;install &lt;/span&gt;awsebcli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Please note, I am really going to take my time on this one. from setup to deployment, so there may be some things you may already know as someone who may have used or is using AWS. And heads up, this is going to be a longgggggg article.&lt;/p&gt;

&lt;p&gt;Alright, let’s get started.&lt;/p&gt;
&lt;h2&gt;
  
  
  AWS Account Setup
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Create an IAM Admin User
&lt;/h2&gt;

&lt;p&gt;The first thing you need to have in mind is… never use your root AWS account for day-to-day work!!!. Instead, create a dedicated admin user:&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Go to your AWS Console and go to IAM (you can as well use the search bar to search for “IAM”)&lt;/li&gt;
&lt;li&gt;Click on "create group"; you can name it "admin" and attach "AdministratorAccess."&lt;/li&gt;
&lt;li&gt;Then in users, create a new user, maybe something like [your-name-admin], and then add it to the admins group&lt;/li&gt;
&lt;li&gt;On the security credentials tab, enable MFA for the user&lt;/li&gt;
&lt;/ol&gt;

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

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

&lt;p&gt;&lt;strong&gt;Upgrade Your AWS Account to Pay-As-You-Go&lt;/strong&gt;&lt;br&gt;
This is one of the most commonly missed steps. New AWS accounts are sometimes placed on a “Free Plan” that silently blocks EC2 instance launches (even t2.micro) with cryptic errors like “instance type not eligible for Free Tier.” I had this issue too.&lt;/p&gt;

&lt;p&gt;How do you fix this? In the AWS Console, go to Billing and Cost Management and look for the upgrade plan button. Click it and confirm.&lt;br&gt;
Without this, Elastic Beanstalk environments may fail to launch with no clear error message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OIDC Authentication for GitHub Actions&lt;/strong&gt;&lt;br&gt;
Instead of storing AWS access keys as GitHub secrets, which is a security anti-pattern, we use OpenID Connect (OIDC). GitHub’s CI runner proves its identity to AWS directly; no long-lived credentials are needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding GitHub as an OIDC Identity Provider&lt;/strong&gt;&lt;br&gt;
Head over to IAM in the AWS console and select Identity providers. Click Add provider.&lt;/p&gt;

&lt;p&gt;Select OpenID Connect as the provider type.&lt;br&gt;
token.actions.githubusercontent.com as the provider URL&lt;br&gt;
and sts.amazonaws.com as the audience&lt;/p&gt;

&lt;p&gt;Press enter&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Create the GitHub Actions IAM role.&lt;/strong&gt;&lt;br&gt;
Still on the IAM page, click on “Roles” under Access Management. Click on the Create Role button.&lt;br&gt;
Here is the information you will need.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trusted entity type: Web Identity&lt;/li&gt;
&lt;li&gt;Identity Provider: token.actions.githubusercontent.com&lt;/li&gt;
&lt;li&gt;Audience: sts.amazonaws.com&lt;/li&gt;
&lt;li&gt;Attach policy: AdministratorAccess (you can scope this down to least privilege once you're stable).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After creation, you may need to edit the trust policy to scope it to your specific repository. See code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Federated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repo:YOUR_ORG/YOUR_REPO:*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the role ARN; you’ll need to add this to GitHub Secrets as AWS_ROLE_ARN.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Up next? RDS MySQL Database 😆
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Let's create this database.&lt;/strong&gt;&lt;br&gt;
On the AWS console, search for RDS, enter RDS, and click "Select Database."&lt;/p&gt;

&lt;p&gt;Now fill in the following details.&lt;/p&gt;

&lt;p&gt;Engine: MySQL&lt;br&gt;
Template: Dev/Test (this disables Multi-AZ and saves cost)&lt;br&gt;
DB instance class: db.t4g.micro&lt;br&gt;
Storage: 20 GB gp3&lt;br&gt;
VPC: default VPC&lt;br&gt;
Public access: No (the app connects internally; never!) ever! expose RDS to the internet)&lt;br&gt;
Create a new security group: [your-app]-rds-sg&lt;br&gt;
Initial database name: [your_app]_staging&lt;br&gt;
You will need to take note of the endpoint hostname (an example is your-db.xxxxx.us-east-1.rds.amazonaws.com), port (usually 3306), username, and password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's talk about the security group too (I think this is a critical detail for me).&lt;/strong&gt;&lt;br&gt;
The RDS security group needs an inbound rule allowing MySQL (port 3306) from the EC2 security group that Elastic Beanstalk will assign to your instance… not an IP address (CIDR).&lt;/p&gt;

&lt;p&gt;A CIDR-based inbound rule (like 0.0.0.0/0 or “My IP”) will not work correctly for EB-to-RDS communication. You will get connection timeouts. The rule must reference the EC2 security group by its ID. We’ll come back to this after the EB environment is created.&lt;/p&gt;
&lt;h2&gt;
  
  
  Now to the main part… Elastic Beanstalk Setup 😁
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Creating EB Service Roles&lt;/strong&gt;&lt;br&gt;
Before creating an EB environment, two IAM roles are needed:&lt;/p&gt;

&lt;p&gt;The Service role (EB in itself):&lt;/p&gt;

&lt;p&gt;Trusted entity elasticbeanstalk.amazonaws.com&lt;br&gt;
Use case: Environment&lt;br&gt;
Name: aws-elasticbeanstalk-service-role&lt;br&gt;
EC2 instance role (your actual server):&lt;/p&gt;

&lt;p&gt;Trusted entity: EC2&lt;br&gt;
Use case: Elastic Beanstalk-Compute&lt;br&gt;
Name: aws-elasticbeanstalk-ec2-role&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Creating the EB Application and Environment&lt;/strong&gt;&lt;br&gt;
Application name: your-app-backend&lt;br&gt;
Environment name: your-app-backend-staging&lt;br&gt;
Platform: PHP (choose the PHP version matching your composer.json PHP constraint—check with php -v locally)&lt;br&gt;
Instance type: t2.micro (this is free-eligible, about $8–10/month)&lt;br&gt;
Single instance (no load balancer — this is what keeps costs low)&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Set the Document Root&lt;/strong&gt;&lt;br&gt;
Just a quick notice….&lt;br&gt;
Laravel’s entry point is public/index.php, not the project root. By default, EB’s nginx config serves from the project root, which causes a 403 Forbidden on every request.&lt;/p&gt;

&lt;p&gt;You can fix this via the official EB setting (you don’t need any extra nginx configuration).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the Nginx rewrite rule.&lt;/strong&gt;&lt;br&gt;
Even with the document root set, Nginx needs a rewrite rule to send all non-file requests to index.php (Laravel’s front controller). Without this, every route except / returns 404.&lt;/p&gt;

&lt;p&gt;Create this file in your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.platform/nginx/conf.d/elasticbeanstalk/01_laravel.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with this code...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&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;Commit and push this file to your deploy branch. This file is minimal by design: it only adds the rewrite and lets EB’s platform-managed config handle everything else (document root, PHP-FPM connection, and fastcgi params).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set Environment Variables&lt;/strong&gt;&lt;br&gt;
Rather than shipping an .env file (insecure), inject all config as EB environment properties. The fastest way is via the EB CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# If its first time setup, run from your project root&lt;/span&gt;
eb init        &lt;span class="c"&gt;# select us-east-1, select your application&lt;/span&gt;
eb use your-app-backend-staging

&lt;span class="c"&gt;# Then set all variables in one command&lt;/span&gt;
eb setenv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;APP_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;APP_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;base64&lt;/span&gt;:your-generated-key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;APP_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;APP_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://staging-api.yourdomain.com &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB_CONNECTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysql &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-db.xxxxx.us-east-1.rds.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3306 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_app_staging &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_db_user &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_db_password &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;CACHE_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;file &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SESSION_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;file &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;QUEUE_CONNECTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;LOG_CHANNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;stack &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="c"&gt;# ... add all provider keys here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some gotchas...&lt;/p&gt;

&lt;p&gt;EB has a ~4096 character limit on the combined environment variable payload passed through CloudFormation. If you have very long values (e.g. a JWT token over 2000 characters), eb setenv will fail with the template format error: Parameter ‘EnvironmentVariables’ default value length is greater than 4096. Drop the oversized variable and add it separately via AWS Secrets Manager or SSM Parameter Store instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions CI/CD Pipeline
&lt;/h2&gt;

&lt;p&gt;Create .github/workflows/deploy-staging.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to AWS Staging&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials via OIDC&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install PHP dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;composer install --no-dev --optimize-autoloader --no-interaction&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create deployment package&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;zip -r deploy.zip . \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude "*.git*" \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude ".github/*" \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude "node_modules/*" \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude "tests/*" \&lt;/span&gt;
            &lt;span class="s"&gt;--exclude ".env*"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to S3&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;VERSION_LABEL="v-$(date +%Y%m%d%H%M%S)"&lt;/span&gt;
          &lt;span class="s"&gt;aws s3 cp deploy.zip s3://elasticbeanstalk-us-east-1-YOUR_ACCOUNT_ID/deploy.zip&lt;/span&gt;
          &lt;span class="s"&gt;echo "VERSION_LABEL=$VERSION_LABEL" &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create EB application version&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws elasticbeanstalk create-application-version \&lt;/span&gt;
            &lt;span class="s"&gt;--application-name your-app-backend \&lt;/span&gt;
            &lt;span class="s"&gt;--version-label ${{ env.VERSION_LABEL }} \&lt;/span&gt;
            &lt;span class="s"&gt;--source-bundle S3Bucket=elasticbeanstalk-us-east-1-YOUR_ACCOUNT_ID,S3Key=deploy.zip&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Elastic Beanstalk&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws elasticbeanstalk update-environment \&lt;/span&gt;
            &lt;span class="s"&gt;--environment-name your-app-backend-staging \&lt;/span&gt;
            &lt;span class="s"&gt;--version-label ${{ env.VERSION_LABEL }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add AWS_ROLE_ARN to your GitHub repository secrets (Settings → Secrets and variables → Actions).&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Fix the RDS security group.&lt;/strong&gt;&lt;br&gt;
After your first successful deploy, the EC2 instance’s security group ID will be visible. You need to allow it in the RDS security group:&lt;/p&gt;

&lt;p&gt;EB Console → Configuration → Instance traffic and scaling → Note the EC2 security group ID (format: sg-xxxxxxxxxx)&lt;br&gt;
EC2 Console → Security Groups → Find your-app-rds-sg → Inbound Rules → Edit Inbound Rules&lt;br&gt;
Now here’s where it gets tricky…&lt;/p&gt;

&lt;p&gt;The natural instinct is to type your IP address as the source. Don’t do this.&lt;/p&gt;

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

&lt;p&gt;CIDR-based inbound rules cause connection timeouts for EB-to-RDS communication. And when you try to switch an existing CIDR rule to a security group reference, AWS throws that error above. This is because the rule types are fundamentally different. You can’t convert one to the other in place.&lt;/p&gt;

&lt;p&gt;The fix:&lt;/p&gt;

&lt;p&gt;Delete the existing CIDR rule entirely&lt;br&gt;
Click Add rule&lt;br&gt;
Type: MySQL/Aurora, Port: 3306, Source: Custom → paste/select the EC2 security group ID&lt;br&gt;
Save rules&lt;/p&gt;

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

&lt;p&gt;Important: Every time your EB environment is recreated (e.g, when adding SSH access, changing platform version, or running eb ssh --setup), check this rule. The EC2 security group ID can change, and you'll get a Connection timed out error that looks completely unrelated to what you just did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Migrations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EB doesn’t give you a terminal out of the box. To run php artisan commands, you need SSH access first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eb ssh &lt;span class="nt"&gt;--setup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This terminates and recreates your EC2 instance. There will be a brief downtime on staging; it's completely fine.&lt;/p&gt;

&lt;p&gt;Once connected, there’s another gotcha nobody tells you about: EB environment variables aren’t loaded in your SSH session. The web server process gets them injected automatically, but your interactive shell doesn’t. You’ll know this hit you when you run. Artisan and see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQLSTATE[HY000] [2002] Connection refused
Host: 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 127.0.0.1 is the giveaway… it's the local fallback, not your RDS endpoint.&lt;/p&gt;

&lt;p&gt;Fix it by sourcing the EB env file manually every SSH session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /opt/elasticbeanstalk/deployment/env | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^/export /'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/eb_env.sh
&lt;span class="nb"&gt;source&lt;/span&gt; /tmp/eb_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it worked:&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="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;tinker&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"echo config('database.connections.mysql.host');"&lt;/span&gt;
&lt;span class="c1"&gt;# Should print your RDS endpoint, not 127.0.0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then navigate to the app directory and run migrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/app/current
php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The --force flag is required because Laravel blocks migrations in production environments without explicit confirmation.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Automate Migrations on Every Deploy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don’t want to SSH in every time you add a migration. EB supports post-deploy hooks—scripts that run automatically after every deploy. Create this file in your repo:&lt;/p&gt;

&lt;p&gt;.platform/hooks/postdeploy/01_migrate.sh&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/app/current
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/elasticbeanstalk/deployment/env &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /opt/elasticbeanstalk/deployment/env | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^export //'&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi
&lt;/span&gt;php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable and commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .platform/hooks/postdeploy/01_migrate.sh
git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add postdeploy hook to auto-run migrations"&lt;/span&gt;
git push origin dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this point on, every push dev automatically runs pending migrations after the new code deploys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect Your Domain&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your frontend is on Vercel, your domain’s DNS is managed there and not in your domain registrar’s panel. Namecheap (or wherever you registered) is just the registrar. The actual DNS records need to go into Vercel Dashboard → your team → Domains. yourdomain.app → DNS Records:&lt;/p&gt;

&lt;p&gt;NameTypeValueTTLstaging-api CNAME your-app-staging.eba-xxxxxxxx.us-east-1.elasticbeanstalk.com60&lt;/p&gt;

&lt;p&gt;If a conflicting A record already exists for it, delete it first. A CNAME and A record cannot coexist on the same name. Vercel will show an error if you try.&lt;/p&gt;

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

&lt;p&gt;Confirm DNS is propagated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping staging-api.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ping will always timeout: EC2 blocks ICMP by default. That’s normal. What matters is that it resolves to your EB instance IP address. If you see the IP, DNS is working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding SSL with Let’s Encrypt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since we’re running single-instance EB without a load balancer, we can’t use ACM certificates directly (those attach to ALB listeners). Instead, we use Certbot to get a free Let’s Encrypt certificate that runs on the instance itself.&lt;/p&gt;

&lt;p&gt;Create .platform/hooks/postdeploy/02_certbot.sh:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"staging-api.yourdomain.com"&lt;/span&gt;
&lt;span class="nv"&gt;EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your@email.com"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; certbot &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; certbot python3-certbot-nginx
&lt;span class="k"&gt;fi
if &lt;/span&gt;certbot certificates 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;certbot renew &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--nginx&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--non-interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--agree-tos&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--email&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--domains&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--redirect&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also open port 443 in the EB EC2 security group automatically. Create .ebextensions/https-sg.config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sslSecurityGroupIngress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::SecurityGroupIngress&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GroupId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fn::GetAtt"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSEBSecurityGroup&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GroupId&lt;/span&gt;
      &lt;span class="na"&gt;IpProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
      &lt;span class="na"&gt;ToPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
      &lt;span class="na"&gt;FromPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
      &lt;span class="na"&gt;CidrIp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Use "Fn::GetAtt" with the list syntax, EB's YAML parser rejects the shorthand and gives you this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invalid Yaml: could not determine a constructor for the tag !GetAtt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I spent more time on this than I’d like to admit. 😅&lt;/p&gt;

&lt;p&gt;Make the hook executable and push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .platform/hooks/postdeploy/02_certbot.sh
git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Let's Encrypt SSL via Certbot and open port 443"&lt;/span&gt;
git push origin dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DNS CNAME must be live and propagated before this hook runs. Certbot verifies domain ownership by making an HTTP request to your domain. If the domain doesn’t resolve to your server yet, the cert request will fail.&lt;/p&gt;

&lt;p&gt;One more nginx gotcha (I promise this is the last one).&lt;/p&gt;

&lt;p&gt;Even after Certbot successfully gets the cert, you might still see ERR_CONNECTION_REFUSED when hitting the HTTPS URL. This happened to me: Certbot installed the cert, but nginx wasn't actually listening on port 443.&lt;/p&gt;

&lt;p&gt;Check it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'80|443'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only see port 80 and not 443, Nginx didn’t pick up Certbot’s SSL config. The fix is to manually create the HTTPS server block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;sudo&lt;/span&gt; &lt;span class="s"&gt;tee&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/conf.d/https.conf&lt;/span&gt; &lt;span class="s"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;/dev/null&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;'EOF'&lt;/span&gt;
&lt;span class="s"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;staging-api.yourdomain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/staging-api.yourdomain.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/staging-api.yourdomain.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;HIGH:!aNULL:!MD5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/html/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.(php|phar)(/.*)?$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;php-fpm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/fastcgi_params&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;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;staging-api.yourdomain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;EOF&lt;/span&gt;
&lt;span class="s"&gt;sudo&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt; &lt;span class="s"&gt;-t&lt;/span&gt; &lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s"&gt;sudo&lt;/span&gt; &lt;span class="s"&gt;systemctl&lt;/span&gt; &lt;span class="s"&gt;reload&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if port 443 is now listening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;sudo ss -tlnp | grep -E '80|443'
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LISTEN 0   511   0.0.0.0:443   ← this is what you want to see
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LISTEN 0   511   0.0.0.0:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. You’re live.&lt;/p&gt;

&lt;p&gt;To recap what we built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel app deployed to AWS Elastic Beanstalk (single instance, ~$15–25/mo)&lt;/li&gt;
&lt;li&gt;GitHub Actions CI/CD via OIDC: no static AWS keys stored anywhere&lt;/li&gt;
&lt;li&gt;RDS MySQL with proper security group configuration&lt;/li&gt;
&lt;li&gt;Automated migrations on every deploy via postdeploy hooks&lt;/li&gt;
&lt;li&gt;Free SSL via Let’s Encrypt/Certbot&lt;/li&gt;
&lt;li&gt;Custom domain connected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common Errors and Fixes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had the liberty to draft out a few errors that you may experience during this deployment process, and I have suggested some solutions too.&lt;/p&gt;

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

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

&lt;p&gt;And yeah, I guess… we are done. 😂🎉&lt;/p&gt;

&lt;p&gt;Whew!!! That was a long one, and I do hope you found one or two things helpful. I am also curious about how you handle your own deployments too with Docker and all the other approaches that aid development.&lt;/p&gt;

&lt;p&gt;Happy Cost savingg!!&lt;/p&gt;

&lt;p&gt;You can connect with me on Twitter: &lt;a href="https://twitter.com/mabadejedanphp" rel="noopener noreferrer"&gt;https://twitter.com/mabadejedanphp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>aws</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Writing Smart Contracts in PHP — Bringing PHP to the Blockchain</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Thu, 23 Apr 2026 20:15:55 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/writing-smart-contracts-in-php-bringing-php-to-the-blockchain-5c8n</link>
      <guid>https://dev.to/danielmabadeje/writing-smart-contracts-in-php-bringing-php-to-the-blockchain-5c8n</guid>
      <description>&lt;p&gt;What if PHP developers didn’t have to leave their ecosystem to build in Web3?&lt;/p&gt;

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

&lt;p&gt;I have been writing PHP for years. Laravel, Filament, APIs, admin systems, etc. I have written applications in JavaScript and some in .NET too. But PHP has been my home. But like a lot of PHP developers, I noticed that whenever blockchain came up in a conversation, it felt like a conversation happening in another room. The Web3 space has its own languages, its own tooling, its own ecosystem. &lt;a href="https://www.soliditylang.org/" rel="noopener noreferrer"&gt;Solidity for Ethereum&lt;/a&gt;. &lt;a href="https://solana.com/docs/programs/rust" rel="noopener noreferrer"&gt;Rust for Solana&lt;/a&gt;. &lt;a href="https://aptos.dev/network/blockchain/move" rel="noopener noreferrer"&gt;Move for Aptos&lt;/a&gt;. If you wanted to build smart contracts, the unspoken assumption was: learn a new language first (most likely Rust or Solidity 😂). But I kept wondering… does it have to be that way?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Web3 PHP Packages Already Exist
&lt;/h2&gt;

&lt;p&gt;Before I go further, I want to be clear: PHP is not absent from Web3. There are already some excellent packages that let you interact with blockchains from PHP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/web3p/web3.php" rel="noopener noreferrer"&gt;web3p/web3.php&lt;/a&gt; : connect to Ethereum nodes, sign transactions, call contracts&lt;br&gt;
&lt;a href="https://github.com/Attestto-com/solana-php-sdk" rel="noopener noreferrer"&gt;php-solana-sdk&lt;/a&gt; : interact with the Solana blockchain&lt;br&gt;
&lt;a href="https://packagist.org/packages/kornrunner/keccak" rel="noopener noreferrer"&gt;kornrunner/keccak&lt;/a&gt; : Keccak-256 hashing in pure PHP&lt;/p&gt;

&lt;p&gt;These packages are great. Using them, a PHP developer can read from a smart contract, send transactions, listen to events, and build blockchain-powered applications entirely in PHP. In fact, I tried building a &lt;a href="https://github.com/DanielMabadeje/Web3-giveaway-app-laravel" rel="noopener noreferrer"&gt;web3 giveaway app&lt;/a&gt; at some point to test how I can implement transactions with Solana, Ethereum, and Bitcoin (Solana was successful though 😂).&lt;/p&gt;

&lt;p&gt;But there is a ceiling. You can talk to smart contracts. You cannot write them in PHP.&lt;/p&gt;

&lt;p&gt;There is a big barrier…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual Machine Compatibility:&lt;/strong&gt; Popular blockchains like Ethereum run on the EVM, which only executes bytecode compiled from languages like Solidity or Vyper. PHP is a server-side scripting language meant to be interpreted by the PHP engine (Zend VM), not a blockchain VM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of Determinism:&lt;/strong&gt; Smart contracts must be &lt;a href="https://ethereum.stackexchange.com/questions/3557/why-do-smart-contract-languages-need-to-be-deterministic" rel="noopener noreferrer"&gt;deterministic&lt;/a&gt;, meaning they must produce the exact same result on every computer in the network. PHP has many features (like random number generation and time-based functions) that could cause different nodes to reach different results, breaking blockchain consensus. You can have some more information written by &lt;a href="https://www.linkedin.com/in/ramrapolu/" rel="noopener noreferrer"&gt;Ramchandhar Rapolu&lt;/a&gt; &lt;a href="https://www.linkedin.com/pulse/what-does-deterministic-mean-blockchain-ramchandhar-rapolu-qlvae/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The moment you need to deploy a new contract, you leave the PHP world and open a .sol file. You need to learn Solidity. You need to understand the EVM type system. You need to configure Hardhat or Foundry.&lt;/p&gt;

&lt;p&gt;This gap is what I am trying to close (or shorten, at least)&lt;/p&gt;
&lt;h2&gt;
  
  
  The Inspiration: NativePHP!!
&lt;/h2&gt;

&lt;p&gt;If you are in the PHP community, you have probably heard of &lt;a href="https://nativephp.com/" rel="noopener noreferrer"&gt;NativePHP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The idea behind it is elegant: you already know PHP and Laravel. What if you could use those exact skills to build native mobile and desktop applications without learning Swift, Kotlin, or React Native?&lt;/p&gt;

&lt;p&gt;NativePHP does not run PHP inside your iPhone. It uses PHP as a syntax and logic layer, compiling or bridging that code into something the native runtime understands. That mental model stuck with me. And one day it just clicked…&lt;/p&gt;

&lt;p&gt;“What if we did the same thing for smart contracts?”&lt;br&gt;
PHP does not need to run on the Ethereum virtual machine. It just needs to be the layer where you write the contract. We parse the PHP, understand its intent, and output valid Solidity.&lt;/p&gt;

&lt;p&gt;That is the idea behind &lt;strong&gt;PHP-Solidity.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What PHP-Solidity Is (and Is Not)
&lt;/h2&gt;

&lt;p&gt;Let me be direct about what this project does, because the name might suggest something it is not 😂.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP-Solidity is a transpiler.&lt;/strong&gt; You write a PHP class using special EVM types and PHP 8 attributes. The transpiler reads that file, parses it into an abstract syntax tree, and outputs a .sol file with valid Solidity source code, which you then compile and deploy with standard tooling like Hardhat or Foundry.&lt;/p&gt;

&lt;p&gt;PHP never runs on the blockchain. The EVM never executes PHP. What PHP-Solidity gives you is the ability to express a smart contract using syntax you already know, in a language your IDE already understands.&lt;/p&gt;

&lt;p&gt;Here is what it looks like in practice. This is a simple ERC-20 token written entirely in PHP:&lt;br&gt;
&lt;/p&gt;

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

use PhpSolidity\Attributes\Contract;
use PhpSolidity\Attributes\SolidityEvent;
use PhpSolidity\Attributes\Storage;
use PhpSolidity\Attributes\External;
use PhpSolidity\Attributes\View;
use PhpSolidity\Attributes\Constructor;
use PhpSolidity\Attributes\Modifier;
use PhpSolidity\Attributes\Guarded;

#[Contract(license: 'MIT', version: '^0.8.20')]
#[SolidityEvent('Transfer', ['address indexed from', 'address indexed to', 'uint256 value'])]
class MyToken
{
    #[Storage(public: true)]
    private uint256 $totalSupply;

    #[Storage]
    private mapping $balances;

    #[Storage]
    private address $owner;

    #[Constructor]
    public function __construct(uint256 $initialSupply)
    {
        $this-&amp;gt;owner       = msg::sender();
        $this-&amp;gt;totalSupply = $initialSupply;
        $this-&amp;gt;balances[msg::sender()] = $initialSupply;
        Event::emit('Transfer', address::zero(), msg::sender(), $initialSupply);
    }

    #[Modifier]
    private function onlyOwner(): void
    {
        EVM::require(msg::sender() == $this-&amp;gt;owner, "Not the owner");
    }

    #[External, View]
    public function balanceOf(address $account): uint256
    {
        return $this-&amp;gt;balances[$account];
    }

    #[External]
    public function transfer(address $to, uint256 $amount): bool
    {
        EVM::require($this-&amp;gt;balances[msg::sender()] &amp;gt;= $amount, "Insufficient balance");
        $this-&amp;gt;balances[msg::sender()] -= $amount;
        $this-&amp;gt;balances[$to]           += $amount;
        Event::emit('Transfer', msg::sender(), $to, $amount);
        return true;
    }

    #[External, Guarded('onlyOwner')]
    public function mint(address $to, uint256 $amount): void
    {
        $this-&amp;gt;totalSupply   += $amount;
        $this-&amp;gt;balances[$to] += $amount;
        Event::emit('Transfer', address::zero(), $to, $amount);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You run one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/phpsolidity compile contracts/MyToken.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you get this valid, deployable, (beautiful😏) Solidity:&lt;/p&gt;

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

&lt;p&gt;How It Works Under the Hood&lt;br&gt;
The pipeline has four stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Validation&lt;/strong&gt;&lt;br&gt;
Before any parsing happens, the source file is scanned for things that make no sense on a blockchain: float literals (the EVM has no floating point), rand() (unsafe on-chain. Miners can influence block randomness), file_get_contents(), superglobals, and so on. If you write something invalid, you get a clear error immediately:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;2. Parsing&lt;/strong&gt;&lt;br&gt;
The PHP source is parsed using &lt;a href="https://github.com/nikic/php-Parser" rel="noopener noreferrer"&gt;nikic/php-parser&lt;/a&gt;, one of the most mature PHP AST libraries available. This gives us a complete abstract syntax tree of the PHP class. We walk to that tree looking for a class decorated with #[Contract], then extract everything from it: state variables (from PHP 8 attributes on properties), functions (from methods and their attributes), events (from class-level attributes), and modifiers.&lt;/p&gt;

&lt;p&gt;The function bodies are translated line by line&lt;br&gt;
$this-&amp;gt;balances[msg::sender()] becomes balances[msg.sender], EVM::require(…) becomes require(…). Event::emit(‘Transfer’, …) becomes emit Transfer(…).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. AST to ContractNode&lt;/strong&gt;&lt;br&gt;
The PHP AST is converted into our own internal representation, a ContractNode containing FunctionNode, StateVariableNode, EventNode, and ModifierNode objects. This intermediate layer keeps the parser and emitter completely decoupled. In the future, this same AST could target Vyper or Yul instead of Solidity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Emitting Solidity&lt;/strong&gt;&lt;br&gt;
The SolidityEmitter walks the ContractNode and builds the Solidity source string—license header, pragma, state variables, events, modifiers, constructor, and functions. The output is formatted, readable Solidity that you would not be embarrassed to share.&lt;/p&gt;
&lt;h2&gt;
  
  
  The EVM Type System in PHP
&lt;/h2&gt;

&lt;p&gt;One of the trickiest parts of this project is the type system. Solidity has types like uint256, address, bytes32, and mapping that simply do not exist in PHP.&lt;br&gt;
The solution is a set of PHP stub classes in stubs/evm.php. These classes: uint256, address, mapping, msg, and block… exist purely for the IDE and the transpiler. They give your editor autocompletion and type checking, and they give the transpiler something to map.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In your contract file — valid PHP that your IDE understands
private uint256 $totalSupply;
$this-&amp;gt;balances[msg::sender()] = $initialSupply;

// What the transpiler emits
uint256 private totalSupply;
balances[msg.sender] = initialSupply;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is one notable constraint: PHP treats require as a language construct, not a function. You cannot shadow it. So instead of require(condition, “message”), you write EVM::require(condition, “message”).which the transpiler converts to Solidity’s require().&lt;/p&gt;

&lt;h2&gt;
  
  
  The Limits
&lt;/h2&gt;

&lt;p&gt;Am I happy it works on my machine? Yes. 😂🔥&lt;/p&gt;

&lt;p&gt;Are there real limitations? Also yesss&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP-Solidity is a v0.1 project.&lt;/strong&gt; It works, but it is not magic. Here are the real limitations I think you should know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No floating point.&lt;/strong&gt; The EVM has no float type. All arithmetic must be integer-based, scaled by a factor (usually 1e18 for ETH values). This is a Solidity limitation, not ours though (but you should know😉).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supported PHP subset only&lt;/strong&gt;. You cannot use closures, dynamic arrays, reflection, or most of PHP’s standard library inside a contract. The transpiler enforces this with clear errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Body translation is pattern-based&lt;/strong&gt;. The function body translator works by recognising common patterns ($this-&amp;gt;, msg::sender(), etc.) and replacing them. Complex PHP expressions that have no Solidity equivalent will not transpile correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No struct support yet&lt;/strong&gt;. Solidity structs, which map to complex value types are not yet supported. This is on the roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No interface or abstract contract support yet&lt;/strong&gt;. I hope to add this in future versions or leave it open for others who may want to contribute in this way too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You still need Solidity tooling to deploy&lt;/strong&gt;. PHP-Solidity produces .sol files. You still use Hardhat, Foundry, or Remix to compile and deploy them. PHP-Solidity does not replace that step; it just means you write in PHP first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Potential
&lt;/h2&gt;

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

&lt;p&gt;Despite those limits, the potential here is real.&lt;/p&gt;

&lt;p&gt;You have heard this many times but here I go again 😂… “PHP is one of the most widely used languages in the world." Laravel alone powers hundreds of thousands of applications. I personally think that the Web3 ecosystem desperately needs more developers. And right now, the barrier to entry is high because developers have to learn an entirely new language and toolchain before they can contribute anything.&lt;/p&gt;

&lt;p&gt;PHP-Solidity lowers that barrier. A Laravel developer can write their first smart contract on day one, using concepts they already understand: classes, typed properties, methods, and attributes. They can contribute to DeFi projects, NFT platforms, and DAO tooling without spending months learning Solidity from scratch.&lt;/p&gt;

&lt;p&gt;And the design is extensible. The internal AST is language-agnostic. A Vyper emitter, a Yul emitter, or even a Cairo emitter (for StarkNet) could be built on top of the same parser. The PHP syntax layer stays the same; only the output target changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Is Open Source (Come Build With Me)🎉
&lt;/h2&gt;

&lt;p&gt;PHP-Solidity is published on &lt;a href="https://packagist.org/packages/danielmabadeje/php-solidity" rel="noopener noreferrer"&gt;Packagist&lt;/a&gt; and completely open source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require danielmabadeje/php-solidity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GitHub repository is at &lt;a href="https://github.com/DanielMabadeje/php-solidity" rel="noopener noreferrer"&gt;https://github.com/DanielMabadeje/php-solidity&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is meaningful work to do at every level:&lt;/p&gt;

&lt;p&gt;Beginners: add new EVM types (bytes16, bytes4), improve error messages, and write example contracts.&lt;/p&gt;

&lt;p&gt;Intermediate: improve the body translator to handle more PHP expression patterns and add struct support.&lt;/p&gt;

&lt;p&gt;Advanced: build the interface/abstract contract system, explore a Vyper emitter, write a formal test suite.&lt;/p&gt;

&lt;p&gt;The CONTRIBUTING.md in the repo walks through exactly how the transpiler works, how to add a new type, how to add a validator rule, and the code standards we follow.&lt;/p&gt;

&lt;p&gt;We have watched NativePHP prove that PHP developers do not have to abandon their ecosystem to work on mobile applications. The same principle applies here.&lt;/p&gt;

&lt;p&gt;You do not have to learn Solidity to contribute to Web3. You do not have to leave Laravel to build DeFi tooling. PHP-Solidity is an early, honest attempt to build that bridge.&lt;/p&gt;

&lt;p&gt;It is not perfect. It is not complete. But it works (on my laptop and hopefully in yours 😁) and every open source project starts somewhere. If this resonates with you, install the package, write a contract, break something, and open a PR. That is how this gets better.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can connect with me on twitter : &lt;a href="https://twitter.com/mabadejedanphp" rel="noopener noreferrer"&gt;https://twitter.com/mabadejedanphp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>solidity</category>
    </item>
    <item>
      <title>Automating Laravel CI/CD with GitHub Actions on Namecheap</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Tue, 23 Apr 2024 10:18:50 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/automating-laravel-cicd-with-github-actions-on-namecheap-46aa</link>
      <guid>https://dev.to/danielmabadeje/automating-laravel-cicd-with-github-actions-on-namecheap-46aa</guid>
      <description>&lt;p&gt;&lt;strong&gt;Who is this article for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Have you had some struggles hosting your Laravel application on namecheap? Or you have been looking for a way to automate your deployments to your namecheap server? This article is for you.&lt;/p&gt;

&lt;p&gt;Lets talk about Github actions a bit🙂&lt;/p&gt;

&lt;p&gt;GitHub Actions is a powerful tool for automating various workflows and tasks within the GitHub ecosystem. Here are some of the key benefits of using GitHub Actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Continuous Integration and Deployment (CI/CD): GitHub Actions enables you to set up automated CI/CD pipelines, allowing you to build, test, and deploy your applications with ease. You can define custom workflows that trigger actions based on events like pushes, pull requests, or scheduled intervals. (This is what we will be focusing in this article)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flexibility and Customization: GitHub Actions provides a flexible and customizable platform where you can define workflows using YAML syntax. You have granular control over the steps, dependencies, and conditions of your actions, allowing you to create tailored workflows that fit your specific needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seamless Integration with GitHub: Since GitHub Actions is tightly integrated with the GitHub platform, it offers seamless integration with your repositories, issues, pull requests, and other GitHub features. You can leverage this integration to create workflows that interact with your repository, update issues, trigger notifications, and more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability: GitHub Actions is designed to scale with your needs. You can run workflows on different types of virtual machines and specify resource allocation for each job. Additionally, parallel and matrix strategies allow you to distribute workloads across multiple machines, enabling faster execution times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cross-Platform Support: GitHub Actions supports a wide range of platforms and operating systems, including Linux, macOS, and Windows. You can test your applications across different environments, ensuring compatibility and reliability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost-Efficient: GitHub Actions provides a generous amount of free usage every month, allowing you to automate your workflows without incurring additional costs. If you require more resources or have high-demand workflows, you can choose from various pricing options that align with your needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vast Ecosystem and Community Support: GitHub Actions has a large ecosystem of pre-built actions that you can use to perform common tasks. These actions, created by both GitHub and the community, cover a wide range of functionalities, from building and testing applications to deploying to various platforms. You can also create and share your own actions with others.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With all these mentioned, It is safe to say that Github actions is the real IDAN (magic but colloquially means boss.)😂&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So How Do I Setup this Github Actions?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So first of all lets head to our cpanel, you shouls see something like this.&lt;/p&gt;

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

&lt;p&gt;This particular step here should be followed if you want to deploy to a sub domain. If you want to deploy to the main domain itself, you may skip this step&lt;/p&gt;

&lt;p&gt;Click on Subdomains&lt;/p&gt;

&lt;p&gt;Fill in the details for your sub domain and click on create.&amp;nbsp;&lt;br&gt;
This would automatically create the subdomain and also create the document root.&lt;/p&gt;

&lt;p&gt;Sidenote: I prefer to use the subdomain I created as the actual document root. So I can have my subdomain field as danielsub, my document root would be danielsub.myazabox.site.&lt;br&gt;
After we have successfully created the sub domain, we create an FTP account&lt;/p&gt;

&lt;p&gt;What is an FTP Account?🤔&lt;br&gt;
According to Techopedia, A file transfer protocol account (FTP account) is a type of user account that enables the transfer of files with a host computer by using FTP services.&lt;br&gt;
So basically we are creating an account to facilitate file transfer to a server.&lt;/p&gt;

&lt;p&gt;On your CPanel, Navigate to the files section as shown in the image&lt;/p&gt;

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

&lt;p&gt;Click on FTP Accounts. At this point you should have something like this.&lt;/p&gt;

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

&lt;p&gt;Put in your desired login details and ensure you stored those details properly in maybe your notes or somewhere safe (you would need them). Also select domain you want to assign the ftp account to. In this case it would be the subdomain that we created recently.&lt;/p&gt;

&lt;p&gt;When you are done, you can click on the "Create FTP Account" button.&lt;/p&gt;

&lt;p&gt;If you look below, you should see a new ftp account created as shown below&lt;/p&gt;

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

&lt;p&gt;to view your ftp details like Username, server details and in some cases Port. click on "Configure FTP Client"&lt;br&gt;
Now we are done with CPANEL, lets move over to github and perform this magic.&lt;/p&gt;

&lt;p&gt;At this point I believe you already have your Laravel project pushed to Github. If this has not been done, ensure you have pushed your laravel project to your repository. (This deployment procedure works for both personal and organisation account).&lt;/p&gt;

&lt;p&gt;So for the purpose of this article, I will use a laravel application that I have pushed long ago.&lt;br&gt;
Navigate to the repository that houses your laravel project, and click on the actions tab.&lt;/p&gt;

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

&lt;p&gt;Click on "setup new workflow for yourself -&amp;gt; "&lt;/p&gt;

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

&lt;p&gt;This would create a&amp;nbsp;.yml file for you to work with (mostly a main.yml file)&lt;/p&gt;

&lt;p&gt;Copy the code below and paste it in your main.yml&lt;/p&gt;

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

name: Deploy Laravel Project on push
on:
  push:
    branches:
      - main
jobs:
  web-deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Get the latest code
        uses: actions/checkout@v2.3.2
      - uses: actions/setup-node@master
      - name: Copy .env
        run: php -r "file_exists('.env') || copy('.env.example', '.env');"
      - name: Install Dependencies
        run: composer update --ignore-platform-reqs
      - name: Generate key
        run: php artisan key:generate
      - name: Generate storage link
        run: php artisan storage:link
      - name: Directory Permissions
        run: chmod -R 777 storage bootstrap/cache
      - name: 📂 Sync files
        uses: SamKirkland/FTP-Deploy-Action@v4.3.4
        with:
          server: ${{ secrets.YOURSITE_FTP_SERVER}}
          username: ${{ secrets.USERNAME }}
          password: ${{ secrets.YOUR_PASSWORD }}


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

&lt;/div&gt;

&lt;p&gt;You can use the code above in your main.yml or you could go to Samkirkland to view other use cases to deploy your application with Github actions. Note that it is best practice to create secret keys rather than hard coding the values.&lt;/p&gt;

&lt;p&gt;The code above basically checks if there is a new push to the branch you specified. (In this case the main branch). Runs a composer update, generates a key with the laravel commands and also links the storage.&lt;/p&gt;

&lt;p&gt;After this is done, it uploads the files to the document directory in your CPanel. Do not forget to change the username, ftp and password field to their respective values which you saved earlier on. To take it a notch higher, you can store them as SECRETS on github.&lt;/p&gt;

&lt;p&gt;It is Important to note that your first deployment may take a long time to sync its files as it copies the newly created vendor folder (which houses a lot of files).&lt;br&gt;
One more thing, If you are using an Apache server, there may be need to create a&amp;nbsp;.htaccess in your root directory. After the&amp;nbsp;.htaccess file creation, add the following code.&lt;/p&gt;

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

# RewriteEngine on

# # serve existing files in the /public folder as if they were in /
# RewriteCond %{DOCUMENT_ROOT}public%{REQUEST_URI} -f
# RewriteRule (.+) /public/$1 [L]

# # route everything else to /public/index.php
# RewriteRule ^ /public/index.php [L]

# DirectoryIndex index.php
&amp;lt;IfModule mod_rewrite.c&amp;gt;

    RewriteEngine On
    # RewriteBase /public_html/

    RewriteRule ^$ public/index.php [L]

    RewriteRule ^((?!public/).*)$ public/$1 [L,NC]

&amp;lt;/IfModule&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;And Voila!!!🎉🎉🎉🎉🎉&lt;br&gt;
You have successfully deployed a laravel application to your cpanel through github actions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow me on twitter&amp;nbsp;: &lt;a href="https://twitter.com/mabadejedanphp" rel="noopener noreferrer"&gt;https://twitter.com/mabadejedanphp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>cicd</category>
      <category>github</category>
    </item>
    <item>
      <title>How to Preview Laravel Notification Emails</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Tue, 23 Apr 2024 10:13:22 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/how-to-preview-laravel-notification-emails-3f61</link>
      <guid>https://dev.to/danielmabadeje/how-to-preview-laravel-notification-emails-3f61</guid>
      <description>&lt;p&gt;It’s a hassle trying to test your the HTML of your notification class by sending multiple requests to trigger the Notification to your mail. Do you want to preview your emails in the browser and see what they would look like in the recepients mailbox without having to go through the hassles of sending multiple requests to trigger the notifications? Here are a few lines of codes that can help you with that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9odkba8gydmdde56mpzl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9odkba8gydmdde56mpzl.png" alt="Image of laravel notifciation" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Snippet below shows how you can preview your notifications with your web routes. I am assuming you have created a Notification called TemplateNotification (for this example) or you replaced TemplateNotification with your custom class name. In the example below, my notification class did not require any parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::get(‘mailable’, function () {
  return (new App\Notifications\TemplateNotification())-&amp;gt;toMail((object) [])-&amp;gt;render();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s try this with a class that requires a parameter let’s say the UserRegisteredNotification class that requires the User Object. The code would look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::get('mailable', function () {
    $user = App\Models\User::inRandomOrder()-&amp;gt;first();
    return (new App\Notifications\UserRegisteredNotification($user))-&amp;gt;toMail((object) [])-&amp;gt;render();    

});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you observed in the examples above, you would see that I typecasted an empty array as an object to the toMail method. This is because the toMail method is expecting an object as a parameter.&lt;br&gt;
I hope this helps&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow me on twitter : &lt;a href="https://twitter.com/mabadejedanphp"&gt;https://twitter.com/mabadejedanphp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>beginners</category>
      <category>notifications</category>
    </item>
    <item>
      <title>How to create tables on Heroku Postgresql</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Wed, 03 Mar 2021 20:38:39 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/how-to-create-tables-on-heroku-postgresql-1n42</link>
      <guid>https://dev.to/danielmabadeje/how-to-create-tables-on-heroku-postgresql-1n42</guid>
      <description>&lt;p&gt;So you want to deploy this your idea or project for demo on heroku. Probably, you are deploying from Github, then it’s time for you to migrate or create postgres databases and tables, but then you get confused especially when it’s not a laravel application where you can use the console to migrate tables. so you check the documentation and you get even more confused. this article is for you.&lt;br&gt;
I encountered this some time ago and I felt bad so I decided to do an article on this.&lt;br&gt;
Here we are going to cover everything concerning database and table creation starting from :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adding heroku-postgres as an add-on&lt;/li&gt;
&lt;li&gt;Attaching it as a database&lt;/li&gt;
&lt;li&gt;Getting configuration&lt;/li&gt;
&lt;li&gt;Creating table using dataclips&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article I believe you have a heroku account and you have add your project there so we move over to adding heroku-postgres as an add-on&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Heroku-Postgres as an add-on
&lt;/h2&gt;

&lt;p&gt;On the tabs menu you will see the resources link, when clicked should take you to something like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dashboard.heroku.com/apps/%7Byour_app%7D/resources" rel="noopener noreferrer"&gt;https://dashboard.heroku.com/apps/{your_app}/resources&lt;/a&gt;&lt;br&gt;
you will see something like this..&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7h5le44av8nz5wloxj0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7h5le44av8nz5wloxj0.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
so click on find more add-ons and search for Heroku postgres you will see this&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F688s0qz6gxzf4fghtp9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F688s0qz6gxzf4fghtp9d.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
click on heroku postgres which will take you to this page..&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwoiidhvt528chx9bn36b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwoiidhvt528chx9bn36b.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
click on install heroku postgres and add it to your project.&lt;br&gt;
now we move over to attaching it as a database&lt;/p&gt;

&lt;h2&gt;
  
  
  Attaching as a Database
&lt;/h2&gt;

&lt;p&gt;if you check your resources page you will see that heroku-postgres has been installed and you will see a button that says Attach As Database, click on it which will give you your configuration details. Once this is done, check your settings page and click reveal config var you will a url added there. It contains your database name, password, database host, database name. those are the credentials you add to your app to connect to database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Tables Using Dataclips
&lt;/h2&gt;

&lt;p&gt;If you want to know about dataclips you could check &lt;a href="https://devcenter.heroku.com/articles/dataclips" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
So “Heroku Dataclips enable you to create SQL queries for your Heroku Postgres databases and share the results with colleagues, third-party tools, and the public” — heroku.&lt;br&gt;
so see them as saved queries or as endpoints or as shortcodes (for wordpress people) where you write your SQL queries and they are saved and can be ran.&lt;br&gt;
So we going to use this to create a users table with a dataclip&lt;br&gt;
Note: Datclips by default allow users to read only i.e you can write queries to select data from database but you can’t manipulate it.&lt;br&gt;
for you to alter this setting, you need to write this&lt;/p&gt;

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

set transaction read write; 


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

&lt;/div&gt;

&lt;p&gt;before writing your queries.&lt;br&gt;
Now back to creating our table.&lt;br&gt;
so when we select new dataclip it takes us to a page we can write our queries&lt;br&gt;
we select the project we want the query to execute in and also give a title to our dataclip. then we write our query.&lt;/p&gt;

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

CREATE TABLE user( user_id serial PRIMARY KEY, username VARCHAR ( 50 ) UNIQUE NOT NULL, password VARCHAR ( 50 ) NOT NULL, email VARCHAR ( 255 ) UNIQUE NOT NULL, created_on TIMESTAMP NOT NULL, last_login TIMESTAMP );


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

&lt;/div&gt;

&lt;p&gt;once this written click on save and run.&lt;br&gt;
Voila!!&lt;br&gt;
You have created your first table on Heroku..&lt;br&gt;
Congrats!!&lt;br&gt;
If you like this article, feel free to check my profile for more amazing content&lt;br&gt;
also check me on github as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DanielMabadeje" rel="noopener noreferrer"&gt;https://github.com/DanielMabadeje&lt;/a&gt;&lt;br&gt;
Thanks&lt;/p&gt;

</description>
      <category>heroku</category>
      <category>postgres</category>
      <category>sql</category>
      <category>php</category>
    </item>
    <item>
      <title>PHP Getting Started</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Wed, 04 Nov 2020 09:06:41 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/php-getting-started-3536</link>
      <guid>https://dev.to/danielmabadeje/php-getting-started-3536</guid>
      <description>&lt;p&gt;So you want to start PHP?&lt;br&gt;
Welcome, because in this article we will be looking at getting started with  PHP&lt;/p&gt;

&lt;p&gt;First of all what is PHP?&lt;br&gt;
PHP is an acronym for "PHP: Hypertext Preprocessor". PHP is a widely-used, open source scripting language. PHP scripts are executed on the server. That means PHP is a server-side language i.e scripts executed on the server mostly backend.&lt;/p&gt;

&lt;p&gt;Before we go into installation, lets see what PHP can do. PHP can do this and more:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PHP can generate dynamic page content.&lt;/li&gt;
&lt;li&gt;It can create, open, read, write, delete, and close files on 
the server.&lt;/li&gt;
&lt;li&gt;It can collect form data.&lt;/li&gt;
&lt;li&gt;This language also, can send and receive cookies.&lt;/li&gt;
&lt;li&gt;PHP can add, delete, modify data in your database, in other 
words, it can manipulate the database.&lt;/li&gt;
&lt;li&gt;As a server-side language, it can be used to control user-access.&lt;/li&gt;
&lt;li&gt;It can encrypt data.&lt;/li&gt;
&lt;li&gt;PHP files can contain HTML,CSS and Javascript.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;so Why PHP?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PHP runs on various platforms (Windows, Linux, Unix, Mac OS X, 
etc.)&lt;/li&gt;
&lt;li&gt;PHP is compatible with almost all servers used today (Apache, 
IIS, etc.)&lt;/li&gt;
&lt;li&gt;PHP supports a wide range of databases&lt;/li&gt;
&lt;li&gt;PHP is free. Download it from the official PHP resource: 
&lt;a href="http://www.php.net"&gt;www.php.net&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PHP is easy to learn&lt;/li&gt;
&lt;li&gt;It runs efficiently on the server side&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;At the time this article was written the version of PHP was version php 7.&lt;br&gt;
At the moment PHP is much faster than previous PHP versions, it supports new operators and it also has an improved error handling technique.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started with PHP.
&lt;/h2&gt;

&lt;p&gt;To start using PHP, you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find a web host with PHP and MySQL support.&lt;/li&gt;
&lt;li&gt;Install a web server on your own PC, and then install PHP and 
MySQL.
Most web hosts used are Laragon, Xampp, Wamp etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your server has activated support for PHP you do not need to do anything.&lt;/p&gt;

&lt;p&gt;Just create some .php files, place them in your web directory, and the server will automatically parse them for you.&lt;/p&gt;

&lt;p&gt;You do not need to compile anything or install any extra tools.&lt;/p&gt;

&lt;p&gt;Because PHP is free, most web hosts (locally or production) like the ones listed above offer PHP support.&lt;/p&gt;

&lt;p&gt;However, if your server does not support PHP, you will need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;install a web server.&lt;/li&gt;
&lt;li&gt;install PHP.&lt;/li&gt;
&lt;li&gt;install a database, such as MySQL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After doing all this, create a file with &lt;strong&gt;.php&lt;/strong&gt; extension in the folder provided for you to put your projects. E.g In Laragon, every project you want create is in &lt;strong&gt;www&lt;/strong&gt; folder. while in xampp, it is in the &lt;strong&gt;htdocs&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;now lets write our first line of code in the newly created file..&lt;br&gt;
Please note that PHP codes start with &lt;strong&gt;&amp;lt;?php&lt;/strong&gt; and end with &lt;strong&gt;?&amp;gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;so let's try..&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
$txt = "PHP";
echo "I love $txt!";
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Voila!! you have setup PHP on your PC.&lt;/p&gt;

&lt;h2&gt;
  
  
  MATERIALS
&lt;/h2&gt;

&lt;p&gt;In order to get full documentation on PHP&lt;/p&gt;

&lt;p&gt;check &lt;a href="http://www.php.net"&gt;www.php.net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As other languages have their frameworks, so does PHP&lt;/p&gt;

&lt;p&gt;Frameworks like..&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://laravel.com/"&gt;Laravel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeigniter.com/"&gt;CodeIgniter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://symfony.com/"&gt;Symphony&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cakephp.org/"&gt;CakePHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://framework.zend.com/"&gt;Zend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.yiiframework.com/"&gt;Yii&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fuelphp.com/"&gt;Fuel PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.phalcon.io/4.0/en/introduction"&gt;Phalcon&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;and many others&lt;/p&gt;

&lt;p&gt;for tutorials check&lt;br&gt;
 &lt;a href="https://www.w3schools.com/php/default.asp"&gt;https://www.w3schools.com/php/default.asp&lt;/a&gt;&lt;br&gt;
 &lt;a href="https://www.tutorialspoint.com/php/php_introduction.htm"&gt;https://www.tutorialspoint.com/php/php_introduction.htm&lt;/a&gt; &lt;br&gt;
and others..&lt;/p&gt;

&lt;p&gt;for Online Code editors or environments you could check&lt;br&gt;
&lt;a href="https://phpsandbox.io/"&gt;https://phpsandbox.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;feel free to check me out on Github:&lt;a href="https://github.com/DanielMabadeje"&gt;https://github.com/DanielMabadeje&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>How Web Browsers and Servers Communicate</title>
      <dc:creator>Daniel Mabadeje</dc:creator>
      <pubDate>Tue, 03 Nov 2020 16:25:58 +0000</pubDate>
      <link>https://dev.to/danielmabadeje/how-web-browsers-and-servers-communicate-54am</link>
      <guid>https://dev.to/danielmabadeje/how-web-browsers-and-servers-communicate-54am</guid>
      <description>&lt;p&gt;Have you ever wondered how you are able to go to website on the browser and it views it or wondered why the website you opened in country is not allowed in another country? Or have you wanted to have a sneak peak of the behind the scenes that happen on the web? Then this is the right article for you there are protocols in which your browser follows in order to have access to a particular website which we will cover in this article.&lt;/p&gt;

&lt;p&gt;Please note that Web servers are what supply the content for web browsers. What the browser requests, the server delivers through internet network connections.&lt;/p&gt;

&lt;p&gt;In this article we will cover&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client-Server Network Design and the Web&lt;/li&gt;
&lt;li&gt;Network Protocols for Web Browsers and Servers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First of all let me just start by saying Web browsers and servers communicate using TCP/IP. … The browser initiates a TCP connection to the web server or server pool (using port 80 by default) through its IP address as published in DNS. As part of this process, the browser also makes DNS lookup requests to convert the URL to an IP address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client-Server Network Design and the Web
&lt;/h2&gt;

&lt;p&gt;Web browsers and web servers function together as a client-server system. In computer networking, client-server is a standard method for designing applications where data is kept in central locations (server computers) and efficiently shared with any number of other computers (the clients) on request. All web browsers function as clients that request information from websites (servers).&lt;br&gt;
Numerous web browser clients can request data from the same website. Requests can happen at all different times or simultaneously. Client-server systems conceptually call for all requests to the same site to be handled by one server. In practice, however, because the volume of requests to web servers can sometimes grow very large, web servers are often built as a distributed pool of server computers.&lt;br&gt;
For websites popular in different countries around the world, this webserver pool is geographically distributed to help improve the response time to browsers. If the server is closer to the requesting device, the time it takes to deliver the content is faster than if the server were further away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Protocols for Web Browsers and Servers
&lt;/h2&gt;

&lt;p&gt;Web browsers and servers communicate using TCP/IP. Hypertext Transfer Protocol is the standard application protocol on top of TCP/IP supporting web browser requests and server responses.&lt;br&gt;
Web browsers also rely on DNS to work with URLs. These protocol standards enable different brands of web browsers to communicate with different brands of web servers without requiring particular logic for each combination.&lt;br&gt;
Like most internet traffic, web browser and server connections typically run through a series of intermediate network routers.&lt;br&gt;
A basic web browsing session works like this:&lt;br&gt;
A person specifies a URL in a browser.&lt;br&gt;
The browser initiates a TCP connection to the server or server pool (using port 80, by default) through its IP address, as published in DNS. As part of this process, the browser also makes DNS lookup requests to convert the URL to an IP address.&lt;br&gt;
After the server completes the acknowledgment of its side of the TCP connection, the browser sends HTTP requests to the server to retrieve the content.&lt;br&gt;
After the server replies with content for the page, the browser retrieves it from the HTTP packets and displays it accordingly. Content can include embedded URLs for advertising banners or other external content, which in turn triggers the browser to issue new TCP connection requests to those locations. The browser may also save temporary information, called cookies, about its connections to local files on the client computer.&lt;br&gt;
Any errors encountered during the request for the content might appear as HTTP status lines.&lt;br&gt;
Ok so thats basically everything you need to know about client-side and their protocols&lt;br&gt;
I did this with some help from &lt;a href="https://www.quora.com/How-does-a-browser-communicate-with-a-web-server"&gt;https://www.quora.com/How-does-a-browser-communicate-with-a-web-server&lt;/a&gt; and &lt;a href="https://www.lifewire.com/web-browsers-and-web-servers-communicate-817764"&gt;https://www.lifewire.com/web-browsers-and-web-servers-communicate-817764&lt;/a&gt;&lt;br&gt;
You can email me at: &lt;a href="mailto:mabadejedaniel1@gmail.com"&gt;mabadejedaniel1@gmail.com&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/DanielMabadeje/"&gt;Github&lt;/a&gt;: &lt;a href="https://github.com/DanielMabadeje/"&gt;https://github.com/DanielMabadeje/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>servers</category>
      <category>dev</category>
    </item>
  </channel>
</rss>
