<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Carl Alexander</title>
    <description>The latest articles on Forem by Carl Alexander (@carlalexander).</description>
    <link>https://forem.com/carlalexander</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F501112%2F23189429-aa89-4bbf-b88b-d94913d37dee.jpg</url>
      <title>Forem: Carl Alexander</title>
      <link>https://forem.com/carlalexander</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/carlalexander"/>
    <language>en</language>
    <item>
      <title>What’s the difference between headless and serverless WordPress?</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Thu, 14 Oct 2021 18:18:09 +0000</pubDate>
      <link>https://forem.com/carlalexander/whats-the-difference-between-headless-and-serverless-wordpress-182</link>
      <guid>https://forem.com/carlalexander/whats-the-difference-between-headless-and-serverless-wordpress-182</guid>
      <description>&lt;p&gt;In the last few years, two new terms have emerged in the WordPress and larger web development space. Those two terms are headless and serverless. On top of both being terms ending in "-less", they're two terms that aren't very self-explanatory. (Are there still servers with serverless? &lt;a href="https://blog.ymirapp.com/serverless-vs-hosting-wordpress-server/"&gt;Yes.&lt;/a&gt; Is headless some new horror movie category!? Nope!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DqsE1i-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/10/headless.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DqsE1i-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/10/headless.webp" alt="" width="480" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But to make things worse, because you often use these two new development paradigms together, people use the two terms interchangeably or think they’re the same thing. But they’re not! This increases the confusion and suspicion around both paradigms.&lt;/p&gt;

&lt;p&gt;So with that in mind, this article will walk you through both concepts. This should hopefully clear some of the confusion. But we'll also see how both paradigms are changing the WordPress development landscape. Let's go!&lt;/p&gt;

&lt;h1&gt;Headless: decoupling frontend and backend&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Headless_software"&gt;Headless&lt;/a&gt; WordPress websites are the result of the rise in popularity of JavaScript as a full-fledge programming language. As JavaScript popularity grew, new ways to build websites became popular. King among them was the &lt;a href="https://en.wikipedia.org/wiki/Single-page_application"&gt;single-page application&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're not familiar with a single-page application, it's basically a JavaScript application that runs in your browser. If needed, it'll communicate with a backend to get any data it needs or perform other operations such as sending email. With WordPress, that backend is your WordPress installation.&lt;/p&gt;

&lt;p&gt;A single-page application like I described is headless. That's because the frontend (the head in the analogy) is &lt;del&gt;decapitated&lt;/del&gt;decoupled from the backend (the body). This separation between &lt;a href="https://en.wikipedia.org/wiki/Frontend_and_backend"&gt;backend and frontend&lt;/a&gt; is really all that headless means.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OefhDKqX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/10/headless-diagram-1024x364.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OefhDKqX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/10/headless-diagram-1024x364.png" alt="" width="880" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So when we're talking about headless WordPress, it's just a WordPress site where you only use WordPress as a backend. You don't need to use a theme to display your WordPress content. That's done by the frontend application. In that scenario, your major frontend decision on the WordPress side is whether you want to use the &lt;a href="https://developer.wordpress.org/rest-api/"&gt;REST API&lt;/a&gt; or &lt;a href="https://www.wpgraphql.com/"&gt;GraphQL API&lt;/a&gt; to communicate.&lt;/p&gt;

&lt;h1&gt;Serverless: on-demand computing&lt;/h1&gt;

&lt;p&gt;This brings us to &lt;a href="https://en.wikipedia.org/wiki/Serverless_computing"&gt;serverless&lt;/a&gt;. Serverless is a marketing term to describe &lt;a href="https://en.wikipedia.org/wiki/Function_as_a_service"&gt;function as a service&lt;/a&gt;. Functions as a service allow you to run code on demand without the need to manage a server. This is where the serverless name comes from.&lt;/p&gt;

&lt;p&gt;Serverless is an interesting paradigm shift because it allows developers to focus on writing code and not worry about where it'll run. If you're curious about how hosting WordPress using serverless is different vs using a server, you should check out &lt;a href="https://blog.ymirapp.com/serverless-vs-hosting-wordpress-server/"&gt;this article&lt;/a&gt;. The interest in serverless is also tied to the growing popularity of &lt;a href="https://en.wikipedia.org/wiki/Microservices"&gt;microservices architecture&lt;/a&gt;. With serverless, it's easy to create small microservices by just writing some code and deploying it. &lt;/p&gt;

&lt;p&gt;Once deployed, you also don't have to worry about scaling these microservices. Serverless scales automatically. So there's no worry that you'll blow up your server if you get a traffic spike.&lt;/p&gt;

&lt;p&gt;This ability to scale on-demand is a very powerful feature of serverless. It's also why serverless makes for an &lt;a href="https://blog.ymirapp.com/why-serverless-perfect-hosting-woocommerce/"&gt;ideal platform for hosting WooCommerce&lt;/a&gt;. Hosting a WooCommerce site is more demanding than a regular WordPress site and serverless, with its on-demand scaling, is ideal for it.&lt;/p&gt;

&lt;h1&gt;Why are headless and serverless used together?&lt;/h1&gt;

&lt;p&gt;Now, the more interesting question is why are those two terms confused with one another. It's basically because both paradigms are complimentary. Let's go back and look at headless again.&lt;/p&gt;

&lt;p&gt;With headless, you have a JavaScript frontend application that runs in the browser. You don't need a server to host it. You just need to put the code somewhere the browser can download it. (Often it's just on a &lt;a href="https://en.wikipedia.org/wiki/Content_delivery_network"&gt;content delivery network&lt;/a&gt; (CDN).)&lt;/p&gt;

&lt;p&gt;This frontend application needs to communicate with a backend to get any data it needs, make changes to it or perform other operations. This backend needs to be hosted somewhere.&lt;/p&gt;

&lt;p&gt;You could use a server to do that. But you already have a frontend application that doesn't need a server. Why not just go all in and have the backend not use a server as well!? So you'll see a lot of these frontend applications also use a headless &lt;em&gt;and&lt;/em&gt; serverless backend.&lt;/p&gt;

&lt;p&gt;That's why those two terms are often confused with one another.&lt;/p&gt;

&lt;h1&gt;You can have one without the other&lt;/h1&gt;

&lt;p&gt;That said, while this confusion exists, the reality is that you can have a headless WordPress site without a serverless backend. In fact, none of the headless WordPress sites have a serverless backend. They just host WordPress on a server.&lt;/p&gt;

&lt;p&gt;That's because &lt;a href="https://ymirapp.com"&gt;Ymir&lt;/a&gt; is the only platform that lets you deploy WordPress on serverless infrastructure. But at the same time, you can have a serverless WordPress site that isn't headless. The Ymir blog that you're reading this article on uses serverless, but it's not a headless WordPress site.&lt;/p&gt;

&lt;p&gt;So there you have it, headless and serverless, two terms shrouded in mystery, but hopefully, demystified at last!&lt;/p&gt;

</description>
      <category>hosting</category>
    </item>
    <item>
      <title>How serverless helps keep your WordPress site secure</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Tue, 17 Aug 2021 15:56:00 +0000</pubDate>
      <link>https://forem.com/carlalexander/how-serverless-helps-keep-your-wordpress-site-secure-35gc</link>
      <guid>https://forem.com/carlalexander/how-serverless-helps-keep-your-wordpress-site-secure-35gc</guid>
      <description>&lt;p&gt;Anyone who manages WordPress sites knows how critical it is to keep them secure. Every day thousands of sites get hacked. The consequence of that are often disastrous. They range from lost income to Google blacklisting the WordPress site.&lt;/p&gt;

&lt;p&gt;WordPress itself has always been very secure. But we never use WordPress in a vacuum. We install themes and plugins. We need to host the WordPress site somewhere. All these are potential &lt;a href="http://en.wikipedia.org/wiki/Vector_%28malware%29"&gt;attack vectors&lt;/a&gt; that someone can use to hack your WordPress site.&lt;/p&gt;

&lt;p&gt;Because of its nature, &lt;a href="https://blog.ymirapp.com/serverless-php/"&gt;serverless PHP&lt;/a&gt; makes a lot of these attack vectors irrelevant. This is another reason why it can be a great way to host a WordPress site. (Besides the ridiculous scaling potential.) It doesn't matter whether you're using &lt;a href="https://ymirapp.com"&gt;Ymir&lt;/a&gt; or not.&lt;/p&gt;

&lt;p&gt;But how can serverless PHP can help keep your WordPress site secure? Well, the best way to understand is by going over serverless PHP architectural elements and how they affect these attack vectors.&lt;/p&gt;

&lt;h1&gt;Code deployed to serverless PHP is essentially read only&lt;/h1&gt;

&lt;p&gt;With serverless PHP, you can &lt;a href="https://twitter.com/twigpress/status/1395774346111631360"&gt;scale from 0 to thousands of connections in a minute&lt;/a&gt;. To do that, &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt; and other similar services will always use the same code that you deployed with. And that code will always be read-only on the server that executes it.&lt;/p&gt;

&lt;p&gt;This means that all vectors that rely on injecting PHP files into your WordPress installation don't work. We're talking injecting code in your themes, plugins, wp-config file, includes directory, etc. Those are all impossible because a Lambda function is read-only.&lt;/p&gt;

&lt;p&gt;The only folder that isn't read only is &lt;code&gt;/tmp&lt;/code&gt;. But even if someone injected files there, they wouldn’t be able to stick. That's because a Lambda function is ephemeral. It can exist one minute and be gone the next. Not to mention that it's only injected in one of hundreds of Lambda functions that could be running concurrently.&lt;/p&gt;

&lt;p&gt;But for a code injection attacks to work, they need the code to actually stick to the server. And by its nature, serverless PHP doesn't allow this. This is why this whole category of attack vectors is a non-issue with serverless PHP.&lt;/p&gt;

&lt;p&gt;It's also worth noting that this also means that the plugin and theme file editors won't work. This is also another common attack vector for injecting PHP code into your WordPress site.&lt;/p&gt;

&lt;h1&gt;Your uploads directory isn't on a server&lt;/h1&gt;

&lt;p&gt;Another common place for attackers to inject files is the uploads directory. You upload a file to it. And then you can just have PHP execute it by making a request to it.&lt;/p&gt;

&lt;p&gt;That's why it's a common security practice to disallow PHP file execution in the uploads directory. That way, if someone injects a PHP file and requests it, they'll just download the file instead of having PHP execute it. With serverless WordPress, this is the only possible behaviour because your upload directory isn't on a server. It's on &lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hYA6aZ6J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3-1024x173.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hYA6aZ6J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3-1024x173.png" alt=""&gt;&lt;/a&gt;&lt;a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3.png"&gt;Full size&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above is a diagram of the serverless WordPress architecture. (To read more on it, check out &lt;a href="https://blog.ymirapp.com/serverless-wordpress-aws/"&gt;this article&lt;/a&gt;.) As you can see, CloudFront sends all upload file requests to S3. They never make it to the PHP runtime that runs the PHP code.&lt;/p&gt;

&lt;p&gt;Instead, when they make a request to that PHP file, CloudFront will just send the PHP file back. Exactly like if you requested a text file. This makes it impossible for an attacker to remotely execute a PHP file in the uploads directory.&lt;/p&gt;

&lt;h1&gt;Things that can still happen to you&lt;/h1&gt;

&lt;p&gt;Serverless only really protects you from attacks that target the file system. So while serverless will remove these common attack vectors, there are still things that you should be wary about.&lt;/p&gt;

&lt;h2&gt;SQL injection attacks&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/SQL_injection"&gt;SQL injection&lt;/a&gt; is still something you have to worry about. If you have a plugin or theme with such a vulnerability, you can still get attacked that way. The best way to prevent attacks like these is to just follow standard security best practices.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always keep your plugins and themes up to date.&lt;/li&gt;
&lt;li&gt;Always download them from trusted sources.&lt;/li&gt;
&lt;li&gt;Delete plugins and themes that you aren't using.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Brute-force attacks&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Brute-force_attack"&gt;Brute-force attacks&lt;/a&gt; can also still happen to you even if you use serverless. Much like with SQL injection attacks, you can protect yourself against these attacks by following security best practices.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't use the default "admin" username.&lt;/li&gt;
&lt;li&gt;Use a &lt;a href="https://en.wikipedia.org/wiki/Multi-factor_authentication"&gt;two-factor authentication&lt;/a&gt; plugin. (There are a lot of them! This site uses &lt;a href="https://en-ca.wordpress.org/plugins/google-authenticator/"&gt;Google Authenticator&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;Use a strong and uncompromised password. (I built a &lt;a href="https://wordpress.org/plugins/passwords-evolved/"&gt;simple plugin&lt;/a&gt; that helps with this.)&lt;/li&gt;
&lt;li&gt;Disable XML-RPC. (If you're doing everything else, this isn't as much of a problem.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Denial-of-service attacks (DDOS)&lt;/h2&gt;

&lt;p&gt;In a similar vein as brute force attacks, you also have &lt;a href="https://en.wikipedia.org/wiki/Denial-of-service_attack"&gt;denial-of-service attacks&lt;/a&gt; (DDOS). This is something you can prevent using a &lt;a href="https://en.wikipedia.org/wiki/Web_application_firewall"&gt;web application firewall&lt;/a&gt; (WAF). Security plugins often come with a WAF or offer one as an external service.&lt;/p&gt;

&lt;p&gt;If you're using Ymir or just running WordPress yourself on AWS, you can use &lt;a href="https://aws.amazon.com/waf/"&gt;AWS WAF&lt;/a&gt;. This is a full featured WAF with a lot of customizable options. That said, it also comes with a lot of &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html"&gt;pre-configured rules&lt;/a&gt; to help block known threats such as bots. But it also has a WordPress rule to block known vulnerabilities such as XML-RPC.&lt;/p&gt;

&lt;p&gt;Ymir uses CloudFront to do page caching. If you also use CloudFront this way, you get some DDOS protection for free through &lt;a href="https://aws.amazon.com/shield"&gt;AWS Shield&lt;/a&gt;. This will protect you against infrastructure layer attacks, but not application layers. (Also known as &lt;a href="https://en.wikipedia.org/wiki/Denial-of-service_attack#Application_layer_attacks"&gt;layer 7&lt;/a&gt;.) To protect against those, you need a WAF.&lt;/p&gt;

&lt;h1&gt;Worry-free WordPress security&lt;/h1&gt;

&lt;p&gt;None of what we just saw is exclusive to serverless. You can do all the things that serverless helps with using a regular server. You just need to either have access to it to configure it properly or use a WordPress host that uses security best practices.&lt;/p&gt;

&lt;p&gt;The same goes with serverless and security plugins. There's nothing serverless does which you can't do with a security plugin. That said, security plugins are often a heavy-handed solution to security issues. They can often slow down your WordPress site significantly and cause other issues. With serverless, there's no performance impact to having a secure WordPress site.&lt;/p&gt;

&lt;p&gt;And this is pretty much the benefit of serverless security. It's not better than what you can do yourself with a properly configured server and a security plugin. It's just a more worry-free way to keep a WordPress site secure. And there's something to be said to having that peace of mind.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>wordpress</category>
      <category>security</category>
    </item>
    <item>
      <title>Serverless WordPress architecture on AWS</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Tue, 17 Aug 2021 15:35:14 +0000</pubDate>
      <link>https://forem.com/carlalexander/serverless-wordpress-architecture-on-aws-5dc0</link>
      <guid>https://forem.com/carlalexander/serverless-wordpress-architecture-on-aws-5dc0</guid>
      <description>&lt;p&gt;&lt;a href="https://blog.ymirapp.com/serverless-php/"&gt;Serverless PHP&lt;/a&gt; is a new exciting technology that has the potential to remove a lot of the burden of hosting PHP applications. One type of application that has the most to gain from serverless PHP is &lt;a href="https://en.wikipedia.org/wiki/WordPress"&gt;WordPress&lt;/a&gt;. Serverless PHP eases the burden of scaling WordPress while offering the same performance benefits that you'd get with a top tier host.&lt;/p&gt;

&lt;p&gt;To understand how serverless PHP works with WordPress, we'll look at the current state of the modern WordPress server architecture. This architecture has evolved a lot over the last decade. (You can read more about it &lt;a href="https://carlalexander.ca/modern-wordpress-server-stack/"&gt;here&lt;/a&gt;.) Gone are the days of just hosting a WordPress site with an &lt;a href="https://en.wikipedia.org/wiki/Apache_server"&gt;Apache server&lt;/a&gt;! There's a lot more to hosting a WordPress site now.&lt;/p&gt;

&lt;p&gt;The good news is that hosting a serverless WordPress site looks a lot like it does with a modern WordPress server stack. The big change is that you're going to replace a lot of the architecture components with services from a &lt;a href="https://en.wikipedia.org/wiki/Cloud_computing"&gt;cloud&lt;/a&gt; provider.&lt;/p&gt;

&lt;p&gt;Now, the services that you'll use and how they fit together will vary from one cloud provider to another. With serverless PHP, the most popular cloud provider is &lt;a href="https://en.wikipedia.org/wiki/Amazon_Web_Services"&gt;AWS&lt;/a&gt;. That's why we'll focus on serverless WordPress architecture on AWS.&lt;/p&gt;

&lt;p&gt;It's also worth noting that this is the same architecture that you'd get using &lt;a href="https://ymirapp.com"&gt;Ymir&lt;/a&gt;. The only difference is that Ymir takes care of managing it all for you. (That's also why it's a &lt;a href="https://en.wikipedia.org/wiki/DevOps"&gt;DevOps&lt;/a&gt; platform.) But if you don't mind putting all the pieces together yourself or with the help of another tool (such as the &lt;a href="https://en.wikipedia.org/wiki/Serverless_Framework"&gt;serverless framework&lt;/a&gt;, this article will help you achieve that.&lt;/p&gt;

&lt;h1&gt;The modern WordPress server architecture&lt;/h1&gt;

&lt;p&gt;Let's begin with the modern WordPress server architecture. What does it like? Below is a diagram that shows the big picture of what's going on when a browser makes a request for a WordPress page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LM2L3z8h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/modern-server-stack-1024x252.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LM2L3z8h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/modern-server-stack-1024x252.png" alt=""&gt;&lt;/a&gt;&lt;a href="https://blog.ymirapp.com/uploads/2021/05/modern-server-stack.png"&gt;Full size&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Caching before PHP&lt;/h2&gt;

&lt;p&gt;First, the &lt;a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message"&gt;HTTP request&lt;/a&gt; your browser makes is going to hit a &lt;a href="https://en.wikipedia.org/wiki/Web_cache"&gt;HTTP cache&lt;/a&gt;. This HTTP cache could be a combination of a &lt;a href="https://en.wikipedia.org/wiki/Content_delivery_network"&gt;content delivery network&lt;/a&gt; (also known as CDN) like &lt;a href="https://en.wikipedia.org/wiki/Cloudflare"&gt;Cloudflare&lt;/a&gt;, a dedicated server like &lt;a href="https://en.wikipedia.org/wiki/Varnish_(software)"&gt;Varnish&lt;/a&gt; or a page caching plugin. But whatever you're using, the goal is the same.&lt;/p&gt;

&lt;p&gt;You want to cache WordPress responses.&lt;/p&gt;

&lt;p&gt;That's because if PHP (and by extension WordPress) is a lot slower than your HTTP cache. So you want your HTTP responses to come from the HTTP cache as much as possible. If the HTTP cache can't respond to your request, then it'll forward the request to WordPress.&lt;/p&gt;

&lt;h2&gt;Caching on the PHP side&lt;/h2&gt;

&lt;p&gt;Once you hit PHP, things are a bit more straightforward. The higher the PHP version, the faster WordPress runs. That's why you always try to use the highest PHP version that WordPress supports.&lt;/p&gt;

&lt;p&gt;The last point of optimization comes when WordPress makes queries to the &lt;a href="https://en.wikipedia.org/wiki/MySQL"&gt;MySQL database&lt;/a&gt;. These queries often account for most of the slowness that WordPress will have responding to your request. To make WordPress faster, you want to reduce the number of queries WordPress makes as much as possible.&lt;/p&gt;

&lt;p&gt;You can achieve this by using an &lt;a href="https://codex.wordpress.org/Class_Reference/WP_Object_Cache"&gt;object cache plugin&lt;/a&gt;. An object cache plugin stores the results of a MySQL query in a high performance persistent cache. (Usually &lt;a href="https://en.wikipedia.org/wiki/Memcached"&gt;Memcached&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Redis"&gt;Redis&lt;/a&gt;.) It then checks the cache before performing a query and returns the result if it was present. (Much like the HTTP cache does for our HTTP requests.)&lt;/p&gt;

&lt;p&gt;This drastically reduces the time that WordPress takes to respond to a request. Especially if your WordPress project has plugins that make a lot of queries such as with WooCommerce. That's why it's an essential component of the modern WordPress server architecture.&lt;/p&gt;

&lt;h1&gt;Serverless WordPress architecture&lt;/h1&gt;

&lt;p&gt;Now that we've gone over the modern WordPress server architecture, let's look how it translates to serverless. The reason we went over it is that the essential components stay the same. The difference is that all these components are going to be handled by specific AWS services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_-K7vaqY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-1024x138.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_-K7vaqY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-1024x138.png" alt=""&gt;&lt;/a&gt;&lt;a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress.png"&gt;Full size&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;HTTP cache using CloudFront&lt;/h2&gt;

&lt;p&gt;So let's go back to our browser, the first thing that your HTTP request is going to hit is &lt;a href="https://en.wikipedia.org/wiki/Amazon_CloudFront"&gt;CloudFront&lt;/a&gt;. CloudFront is the AWS content delivery network. You can use it to do a lot of different things for your serverless WordPress site.&lt;/p&gt;

&lt;p&gt;WordPress sites only use CloudFront (or any other CDN) to cache assets like CSS/JS files and media files. But you can also use it as a HTTP cache. This is where the real performance gains happen.&lt;/p&gt;

&lt;p&gt;Using CloudFront as a HTTP cache will significantly reduce the response time of your WordPress site. But because CloudFront is also a content delivery network, this reduction applies to all visitors regardless of where they are in the world. This is a big difference compared to most WordPress HTTP cache solutions which aren't globally distributed.&lt;/p&gt;

&lt;p&gt;If you'd rather not use CloudFront to do page caching, you can also configure it to just cache CSS/JS files and media files. You're also free to not use CloudFront and either use another content delivery network or none. But beware that performance won't be nearly as good without a content delivery network.&lt;/p&gt;

&lt;h2&gt;On the way to Lambda: API Gateway or Elastic Load Balancing&lt;/h2&gt;

&lt;p&gt;If CloudFront can't respond to a request, it'll forward the request to your serverless PHP application hosted on AWS Lambda. Now, much like a regular PHP application, a serverless PHP application isn't exposed to the internet. You need to use another service to act as an intermediary between the two.&lt;/p&gt;

&lt;p&gt;With a regular PHP application, this would be the role of your web server. But with serverless PHP, there's no web server per se. Instead, you need to use one of two services to communicate with AWS Lambda. These are &lt;a href="https://aws.amazon.com/api-gateway/"&gt;API Gateway&lt;/a&gt; or &lt;a href="https://aws.amazon.com/elasticloadbalancing/"&gt;Elastic Load Balancing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In general, you'll want to use an API gateway. (That's why it's the one shown on the diagram.) You should only use a load balancer is when you're dealing with hundreds of million of requests per month. (For example, you'll save several thousand dollars per month between serving a billion requests with a load balancer compared to an API gateway.) But most WordPress sites don't get that much traffic so using an API gateway is the cheaper option.&lt;/p&gt;

&lt;h3&gt;API gateway types&lt;/h3&gt;

&lt;p&gt;AWS also has two different API gateways: &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html"&gt;HTTP API and REST API&lt;/a&gt;. You can use either of them. That said, there are some important differences between the two.&lt;/p&gt;

&lt;p&gt;First, there's the question of cost. The REST API costs more than the HTTP API. That's because the REST API has more features than the HTTP API.&lt;/p&gt;

&lt;p&gt;What are these extra features? Well, it offers http-to-https redirection. But this isn't necessary if you use CloudFront to do page caching. It'll handle the http-to-https redirection.&lt;/p&gt;

&lt;p&gt;It also offers edge optimized caching. But this is also something that CloudFront handles for us. In fact, you can't use both REST API edge optimized caching and CloudFront at the same time.&lt;/p&gt;

&lt;p&gt;A REST API also supports wildcard subdomains. This is only important if you want to host a subdomain &lt;a href="https://wordpress.org/support/article/glossary/#multisite"&gt;multisite&lt;/a&gt; installation. Otherwise, you don't need it either. (You can also work around the HTTP API limitation with wildcard subdomains by using &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html"&gt;CloudFront functions&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;This is all to show you that most of time you don't need the additional REST API features. So you're better off paying less and using the HTTP API instead.&lt;/p&gt;

&lt;h2&gt;Lambda&lt;/h2&gt;

&lt;p&gt;Once the request reaches either an API gateway or a load balancer, it gets converted to an event. This event triggers the Lambda function to run.&lt;/p&gt;

&lt;p&gt;This Lambda function has two components. The PHP runtime which converts the event to a PHP &lt;a href="https://en.wikipedia.org/wiki/FastCGI"&gt;FastCGI&lt;/a&gt; request. The other one is your WordPress project which is what the FastCGI request will run.&lt;/p&gt;

&lt;p&gt;Now, you can create a PHP runtime but it's probably better to use an existing one. &lt;a href="https://github.com/brefphp/bref"&gt;Bref&lt;/a&gt; is the popular open source PHP runtime for AWS Lambda so it's a good choice to pick. Otherwise, &lt;a href="https://github.com/ymirapp/php-runtime"&gt;Ymir's PHP runtime is also open source&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;ElastiCache&lt;/h2&gt;

&lt;p&gt;If you're using an object cache, you'll need a Memcached or Redis cache. &lt;a href="https://aws.amazon.com/elasticache/"&gt;ElastiCache&lt;/a&gt; is the AWS service that manages those. So you'll need to create one if you want to use a persistent object cache.&lt;/p&gt;

&lt;p&gt;If you do decide to use one, be aware that you'll need to put your Lambda function on a private &lt;a href="https://en.wikipedia.org/wiki/Subnetwork"&gt;subnet&lt;/a&gt; to access it. This, in turn, will require a &lt;a href="https://en.wikipedia.org/wiki/Network_address_translation"&gt;NAT&lt;/a&gt; gateway. You can also replace the NAT gateway with a specific EC2 instance that acts as the gateway. (You can read about how to do it &lt;a href="https://www.kabisa.nl/tech/cost-saving-with-nat-instances/"&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;VPC&lt;/h2&gt;

&lt;p&gt;This is a good time to bring up the &lt;a href="https://en.wikipedia.org/wiki/Virtual_private_cloud"&gt;virtual private cloud&lt;/a&gt; also known as &lt;a href="https://aws.amazon.com/vpc/"&gt;VPC&lt;/a&gt;. AWS requires that certain resources such as your cache and database (which we'll see next!) be connected to a VPC. Some have to be on a private subnet (network without access to the internet) while others can also be on a public subnet (network with access to the internet).&lt;/p&gt;

&lt;p&gt;If all you don't use ElastiCache, your interactions with a VPC are minimal. You'll just need a public subnet for the RDS database. Here's an updated diagram showing the VPC without ElastiCache.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lc-77Pu1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-public-subnet-1024x187.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lc-77Pu1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-public-subnet-1024x187.png" alt=""&gt;&lt;/a&gt;&lt;a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-public-subnet.png"&gt;Full size&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Things are a more complicated if you need to use private resources like ElastiCache. This is where you need to add the NAT gateway discussed earlier. But you also need to change certain resources to use the private subnet such as your Lambda function. (By default, Lambda Functions don't need a subnet.)&lt;/p&gt;

&lt;p&gt;You'll also need a &lt;a href="https://en.wikipedia.org/wiki/Bastion_host"&gt;bastion host&lt;/a&gt; to connect to the resources. A bastion host is an EC2 instance that acts as a bridge to the private subnet. For example, a bastion host is necessary if you want to connect to your database server if it's on the private subnet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j2FNgu1y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-private-subnet-1024x257.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j2FNgu1y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-private-subnet-1024x257.png" alt=""&gt;&lt;/a&gt;&lt;a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-private-subnet.png"&gt;Full size&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above is a diagram showing the same architecture with ElastiCache, NAT gateway, bastion host and the private subnet. As you can see, there are more moving parts. It's also more complicated in terms of VPC configuration.&lt;/p&gt;

&lt;h2&gt;RDS&lt;/h2&gt;

&lt;p&gt;The last component of our serverless WordPress architecture is the database. &lt;a href="https://aws.amazon.com/rds/"&gt;RDS&lt;/a&gt; is the service that manages &lt;a href="https://en.wikipedia.org/wiki/Relational_database"&gt;relational databases&lt;/a&gt; like &lt;a href="https://en.wikipedia.org/wiki/MySQL"&gt;MySQL&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/MariaDB"&gt;MariaDB&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you use a public subnet, your database is going to be publicly accessible. This isn’t much a security problem as long as you don’t share the database server address. But this makes the use of a strong password when creating it even more important.&lt;/p&gt;

&lt;p&gt;That said, if it’s on a private subnet, you should set a strong password anyway! But you don’t have to worry about the database being publicly accessible. However, like we mentioned with the VPC, you’ll need to create a bastion host to connect to it.&lt;/p&gt;

&lt;h1&gt;What about media uploads?&lt;/h1&gt;

&lt;p&gt;One thing we haven't talked about yet is uploads. What happens to uploads when you don't have a server? Well, that part is a lot more complicated than what we've seen so far!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hYA6aZ6J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3-1024x173.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hYA6aZ6J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3-1024x173.png" alt=""&gt;&lt;/a&gt;&lt;a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3.png"&gt;Full size&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above is an updated diagram showing you what our serverless WordPress architecture looks like with media uploads. Things start off the same with requests going through CloudFront. Once the request hits CloudFront, it'll decide whether this is a serverless PHP request or a request for a media file. (Not just an uploaded file)&lt;/p&gt;

&lt;p&gt;If it's a media file, the request instead goes to &lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; which is the most well known AWS service. It's a file storage service that lets you store files in the cloud inexpensively. There are a lot of WordPress plugins that let you upload files to S3.&lt;/p&gt;

&lt;h2&gt;How most S3 plugins work&lt;/h2&gt;

&lt;p&gt;That said, there are some notable differences when you use S3 with serverless WordPress compared to a plugin. The biggest one is how you send your WordPress media files to S3. With a plugin, it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i6jSNU7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/s3-plugin-flow-1024x299.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i6jSNU7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/s3-plugin-flow-1024x299.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You upload a media file to your WordPress server like you would normally without a plugin. The plugin itself takes care to upload the file to S3 once it's on the server. Most of the time, this happens in the background using a &lt;a href="https://developer.wordpress.org/plugins/cron/"&gt;WordPress cron&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;How does file uploads work with S3 and serverless WordPress?&lt;/h2&gt;

&lt;p&gt;With serverless, you can't do that because there's no server to upload the file to first. Instead, you need to upload it to S3 right away. The proper way to do that is to use a &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html"&gt;signed URL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qSnOtgzD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/s3-plugin-upload-flow-1024x360.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qSnOtgzD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/05/s3-plugin-upload-flow-1024x360.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A signed URL is a temporary URL that you can use to perform an operation on S3. So what you need to do is contact S3 and it'll create a signed URL. You then use this signed URL to upload the file to S3 without compromising your security.&lt;/p&gt;

&lt;p&gt;This is a lot harder to do in practice since WordPress doesn't make it easy to take over this process. You also don't have direct access to the files on the server to create the different file sizes either. So you need to develop a way to create those as well.&lt;/p&gt;

&lt;p&gt;This is by far the more complicated aspect of the serverless WordPress architecture. It's not really because of anything specific with AWS. It's just because WordPress is an &lt;a href="https://en.wikipedia.org/wiki/Legacy_system"&gt;old piece of software&lt;/a&gt;. Its creators designed it assuming you'd always have a server to upload files to. (The same with most S3 upload plugins as well.) There's no simple way to tell it you don't want to do it.&lt;/p&gt;

&lt;p&gt;That said, WordPress is very extensible. You can write a plugin (like the &lt;a href="https://github.com/ymirapp/wordpress-plugin"&gt;Ymir one&lt;/a&gt;) that can hijack the WordPress media upload process. (Another plugin that supports direct upload is &lt;a href="https://en-ca.wordpress.org/plugins/ilab-media-tools/"&gt;Media Cloud&lt;/a&gt;.) That's what's necessary for it to work with serverless.&lt;/p&gt;

&lt;h1&gt;Different, but also familiar&lt;/h1&gt;

&lt;p&gt;As you can see, a serverless WordPress installation isn't quite the same as a modern WordPress server architecture. But it's grounded in the same fundamentals. It's just that you have to rely on services from cloud providers like AWS.&lt;/p&gt;

&lt;p&gt;But that's also what a lot of hosts already do. They rely on services like a content delivery network (CloudFront), managed databases (RDS) and managed Redis caches (ElastiCache). So there are some similar elements between the two.&lt;/p&gt;

&lt;p&gt;But as the issue with media uploads highlighted, a lot of the architectural challenges with serverless come from WordPress itself. It's not that it can't run on Lambda. It's that some of its inner workings assume there will always be a server present.&lt;/p&gt;

&lt;p&gt;That said, overcoming these issues can be worthwhile. This is especially true if you need to scale WordPress or, even more so, WooCommerce. Serverless offers unparalleled scaling potential. That's also why it's the &lt;a href="https://blog.ymirapp.com/why-serverless-perfect-hosting-woocommerce/"&gt;ideal platform for WooCommerce&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>wordpress</category>
      <category>aws</category>
      <category>lambda</category>
    </item>
    <item>
      <title>What is serverless PHP and how does it work?</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Thu, 24 Jun 2021 17:53:34 +0000</pubDate>
      <link>https://forem.com/carlalexander/what-is-serverless-php-and-how-does-it-work-4m5b</link>
      <guid>https://forem.com/carlalexander/what-is-serverless-php-and-how-does-it-work-4m5b</guid>
      <description>&lt;p&gt;Serverless computing is a new cloud computing model centred on &lt;a href="https://en.wikipedia.org/wiki/Function_as_a_service" rel="noopener noreferrer"&gt;Functions as a Service&lt;/a&gt; (FaaS). A serverless PHP application is simply a PHP application that runs on one of those serverless computing platforms. But what's so special about it, and why is there interest in using it instead of a regular server for PHP?&lt;/p&gt;

&lt;p&gt;Well, as web developers, we always have to consider where we host our code. It doesn't matter whether we're using &lt;a href="https://en.wikipedia.org/wiki/JavaScript" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/PHP" rel="noopener noreferrer"&gt;PHP&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Python_(programming_language)" rel="noopener noreferrer"&gt;Python&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Ruby_(programming_language)" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt;. (Just to name a few.) They all need a hosting service where that code can run and render the HTML sent to the browser.&lt;/p&gt;

&lt;p&gt;There are a lot of different hosting services. You can pay for a server on &lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt; or some other some other &lt;a href="https://en.wikipedia.org/wiki/Cloud_computing" rel="noopener noreferrer"&gt;cloud&lt;/a&gt; provider. This is often the cheapest option, but then you have a server to manage.&lt;/p&gt;

&lt;p&gt;If you don't want to do that, you can use a &lt;a href="https://en.wikipedia.org/wiki/Platform_as_a_service" rel="noopener noreferrer"&gt;Platform as a Service&lt;/a&gt; (PaaS) like &lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;. You tell them how big of a server you want and they take care of the rest. On your end, you just need to deploy your code and that's it. &lt;a href="https://en.wikipedia.org/wiki/WordPress" rel="noopener noreferrer"&gt;WordPress&lt;/a&gt; hosting works similarly.&lt;/p&gt;

&lt;p&gt;While platforms as a service help you worry less about your server, they don't completely remove all server issues. You still have to wonder if you can handle spikes in traffic. That's because most of these services won't scale automatically to handle these scenarios.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Serverless_computing" rel="noopener noreferrer"&gt;Serverless computing&lt;/a&gt; offers a solution to this problem. It distills your cloud computing needs to its barest essence. Your cloud provider will run your code on-demand and only charge you for that.&lt;/p&gt;

&lt;p&gt;This lets you only pay for what you use. (If your site receives no traffic, you pay nothing.) This architecture also lets you scale infinitely. In fact, serverless computing can scale to handle thousands of visitors almost instantly.&lt;/p&gt;

&lt;p&gt;This is a transformative change for all programming languages. But even more so for PHP, since it's a language uniquely positioned to leverage the benefits of serverless. Let's explore why that is.&lt;/p&gt;

&lt;h1&gt;How does a traditional PHP application work?&lt;/h1&gt;

&lt;p&gt;PHP differs from most other programming languages because it's an &lt;a href="https://en.wikipedia.org/wiki/Interpreted_language" rel="noopener noreferrer"&gt;interpreted language&lt;/a&gt;. You don't need to compile your code. You can just upload your PHP files to a web server, and that's it.&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%2Fblog.ymirapp.com%2Fuploads%2F2021%2F04%2Fserver-php-architecture.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%2Fblog.ymirapp.com%2Fuploads%2F2021%2F04%2Fserver-php-architecture.png" alt="PHP server architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The web server (&lt;a href="https://en.wikipedia.org/wiki/Nginx" rel="noopener noreferrer"&gt;nginx&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Apache_HTTP_Server" rel="noopener noreferrer"&gt;apache&lt;/a&gt; most of the time) does all the magic. It figures out what PHP file you're trying to access. Often, it's just the &lt;code&gt;index.php&lt;/code&gt; file in the root directory which then loads other PHP files. (Fun fact: &lt;a href="https://twitter.com/levelsio/status/1381709793769979906" rel="noopener noreferrer"&gt;Pieter Levels makes $100k/month from a single index.php file!&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The web server then sends this PHP file to the PHP interpreter. The interpreter reads the file, parses it and then executes it. Once the execution completes, it'll return some output. That output is the generated HTML that the web server sends back to your browser as an HTTP response.&lt;/p&gt;

&lt;h1&gt;What happens when you use serverless PHP&lt;/h1&gt;

&lt;p&gt;Now that we've gotten a basic idea of how a PHP application works, we can look at what happens to it when you use serverless PHP. First, serverless computing is &lt;a href="https://en.wikipedia.org/wiki/Event-driven_programming" rel="noopener noreferrer"&gt;event-driven&lt;/a&gt;. Your serverless PHP interpreter awaits for an event, and this triggers the PHP code to run.&lt;/p&gt;

&lt;p&gt;In a way, it’s similar to how things work with a web server. Your web server receives an &lt;a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message" rel="noopener noreferrer"&gt;HTTP request&lt;/a&gt; (an event) and this event would cause the web server to tell the PHP interpreter to process your code. This is the primary reason serverless computing works so well with PHP.&lt;/p&gt;

&lt;p&gt;So what's different about serverless PHP then? The big change is that, not only do you not have a server with serverless PHP, you don't have a web server anymore either. Instead of a web server, you use an API gateway which is a special service offered by the cloud provider. Let's look at what that looks like in more detail.&lt;/p&gt;

&lt;h2&gt;The life of a serverless PHP request&lt;/h2&gt;

&lt;p&gt;Below is a diagram showing what happens when you make a request to a serverless PHP application. As you can see, it looks very similar to what we had before.&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%2Fblog.ymirapp.com%2Fuploads%2F2021%2F04%2Fserverless-php-architecture.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%2Fblog.ymirapp.com%2Fuploads%2F2021%2F04%2Fserverless-php-architecture.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The browser sends the HTTP request to the API gateway. The gateway receives the HTTP request, converts it to an event, and forwards it to the serverless PHP application. It reads, parses, and executes the requested PHP file and returns the result to the API gateway. The gateway generates the HTTP response and sends it back to the browser.&lt;/p&gt;

&lt;p&gt;Of note is the serverless PHP application. It has two parts: the PHP runtime and your PHP code.&lt;/p&gt;

&lt;p&gt;The PHP runtime contains the PHP interpreter. But it also has code to process the event sent by the API gateway and return it the response from the interpreter. Your PHP application code is the same PHP code you’d deploy to a regular server.&lt;/p&gt;

&lt;p&gt;These two parts get packaged together as a function (this is where the name Function as a Service comes from) when you deploy your serverless PHP application to the cloud provider. This function is what the API gateway sends an event to whenever it receives a request.&lt;/p&gt;

&lt;h1&gt;What's the advantage of this?&lt;/h1&gt;

&lt;p&gt;Now that we've seen how serverless PHP application works, you might wonder, "Why would I want to use this instead of my trusty Linux server?"&lt;/p&gt;

&lt;h2&gt;Near infinite scaling&lt;/h2&gt;

&lt;p&gt;Once you've deployed a new version of your serverless PHP application, the cloud provider can create as many copies of it as necessary. It can also create these copies almost instantly. This allows your PHP application to handle large spikes of traffic without breaking a sweat. (For example, &lt;a href="https://blog.ymirapp.com/why-serverless-perfect-hosting-woocommerce/" rel="noopener noreferrer"&gt;Ymir can help your WooCommerce site scale to handle hundreds of customers instantly&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;With a normal server setup, you need to have the server capacity already provisioned and ready to go. Otherwise, you need to have a &lt;a href="https://en.wikipedia.org/wiki/Horizontal_scalability" rel="noopener noreferrer"&gt;horizontally scaling&lt;/a&gt; setup ready to handle these traffic spikes. But these types of setup take a few minutes to scale which can make the application unavailable until then.&lt;/p&gt;

&lt;h2&gt;No need to manage servers&lt;/h2&gt;

&lt;p&gt;This is a good way to bring up the next advantage of using serverless. You don't have a server to manage anymore. If you've ever had to manage servers, you know the responsibilities that come with managing one. (Being on call when it goes down, handling updates, keeping it secure, etc.)&lt;/p&gt;

&lt;p&gt;With serverless, all that you have to do is deploy your PHP code to your serverless platform and you're good to go! This gives you a peace of mind that's hard to put a price on. But once you've experienced it, it's hard to go back. (It's why I built &lt;a href="https://ymirapp.com" rel="noopener noreferrer"&gt;Ymir&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;Pay per use&lt;/h2&gt;

&lt;p&gt;The server doesn't just change how we host our code. It's also changes how we pay for hosting it. Before you'd pay for a server that had to stay on at all times. This was true regardless that you had a droplet on DigitalOcean or code deployed to a Heroku dyno.&lt;/p&gt;

&lt;p&gt;With serverless, you only pay when your code runs. (For example, &lt;a href="https://en.wikipedia.org/wiki/AWS_Lambda" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; charges you by the millisecond!) In a lot of cases, this offers significant cost saving. &lt;a href="https://mnapoli.fr/serverless-case-study-externals/" rel="noopener noreferrer"&gt;One case study&lt;/a&gt; shows how &lt;a href="https://externals.io/" rel="noopener noreferrer"&gt;external.io&lt;/a&gt; went from paying $50/month on &lt;a href="https://platform.sh" rel="noopener noreferrer"&gt;platform.sh&lt;/a&gt; to ~$17/month on &lt;a href="https://en.wikipedia.org/wiki/Amazon_Web_Services" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;The tradeoffs&lt;/h1&gt;

&lt;p&gt;If serverless sounds too good to be true, that's because it generally is! That said, it's not the ideal solution in all situations. Here are some the tradeoffs you have to consider if you're looking to use serverless,&lt;/p&gt;

&lt;h2&gt;Harder to predict costs&lt;/h2&gt;

&lt;p&gt;When you pay for a server, you know it'll be $X/month. It's simple and easy to plan for. If you need a larger server, you know you'll go from paying $X/month to $Y/month.&lt;/p&gt;

&lt;p&gt;With serverless that whole cost calculation is more complicated, you have to think, "How many requests do I get? How long do they last on average?" So while pay per use pricing model offers great cost saving potential, you might prefer the predictability of regular hosting costs. Even if you might pay 2-3 times more for it.&lt;/p&gt;

&lt;h2&gt;Not cheaper than a low-end VPS&lt;/h2&gt;

&lt;p&gt;If you're running your PHP application on a $5/month &lt;a href="https://en.wikipedia.org/wiki/Virtual_private_server" rel="noopener noreferrer"&gt;VPS&lt;/a&gt;, serverless won't offer any cost saving. In general, serverless PHP becomes cost competitive when you're paying $25/month for hosting. $25/month isn't that expensive for hosting, but a lot of us just host projects on smaller VPCs.&lt;/p&gt;

&lt;p&gt;That said, &lt;a href="https://twitter.com/JackEllis/status/1337425341103521792" rel="noopener noreferrer"&gt;it's also disingenuous to just compare server costs like this&lt;/a&gt;. You should also consider how much it costs to manage that server. Either it costs your time or you have to pay someone to do it.&lt;/p&gt;

&lt;h2&gt;Generally limited to AWS&lt;/h2&gt;

&lt;p&gt;While serverless computing exists on all the major cloud providers, your serverless architecture is very provider dependent. An API gateway on &lt;a href="https://en.wikipedia.org/wiki/Microsoft_Azure" rel="noopener noreferrer"&gt;Microsoft Azure&lt;/a&gt; won't work the same as the one on &lt;a href="https://en.wikipedia.org/wiki/Google_Cloud_Platform" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reality is that, at this time, most of the energy around serverless PHP is focused on one provider: AWS. Not everyone can use AWS for a wide range of reasons. That said, if you can't use AWS, you're going to have a harder time finding resources or services (&lt;a href="http://vapor.laravel.com/" rel="noopener noreferrer"&gt;Laravel Vapor&lt;/a&gt; and Ymir are both AWS only) to create a serverless PHP application.&lt;/p&gt;

&lt;h1&gt;A great alternative to servers&lt;/h1&gt;

&lt;p&gt;The point of this article wasn't necessarily to show you you shouldn't host a PHP application on a server. Hosting a PHP application on a server still has a bright future ahead of it. It's sometimes more cost effective to do so. You can choose which provider you want to use, and there are countless resources and tools to help you manage one.&lt;/p&gt;

&lt;p&gt;But if you're using an expensive hosting provider or a platform as a service or some complex horizontally scaling setup, serverless might be the thing you're waiting for. It's easier to manage and deploy to while often costing less.&lt;/p&gt;

&lt;p&gt;And that's something to be excited about.&lt;/p&gt;

</description>
      <category>php</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Why serverless is the perfect hosting solution for WooCommerce</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Wed, 31 Mar 2021 21:17:44 +0000</pubDate>
      <link>https://forem.com/carlalexander/why-serverless-is-the-perfect-hosting-solution-for-woocommerce-2bc8</link>
      <guid>https://forem.com/carlalexander/why-serverless-is-the-perfect-hosting-solution-for-woocommerce-2bc8</guid>
      <description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Serverless_computing"&gt;Serverless computing&lt;/a&gt; isn't discussed much in the WordPress community. But it has the potential to transform how WordPress sites scale and perform.&lt;/p&gt;

&lt;p&gt;No WordPress platform has more to gain from serverless computing than WooCommerce. Hosting a WooCommerce site comes with unique challenges that you don't have when hosting a regular WordPress site. That's why a lot of managed WordPress hosting companies started to offer dedicated WooCommerce hosting.&lt;/p&gt;

&lt;p&gt;Serverless is the perfect technological solution to tackle those challenges. That's why you'll probably start hearing more and more about it.&lt;/p&gt;

&lt;h1&gt;How do you host a regular WordPress site?&lt;/h1&gt;

&lt;p&gt;But before we go over the main issue with hosting WooCommerce sites, it would be useful to look at how you host a high traffic WordPress site. While the implementation can vary from host to host, the general architecture is essentially the same. It looks like the diagram below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VY6XqZHo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/04/WordPress-host-architecture-1024x217.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VY6XqZHo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/04/WordPress-host-architecture-1024x217.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whenever a browser makes a request for a WordPress page, the first thing that a WordPress host will try to do is serve a cached version of that page. If the WordPress host doesn't do page caching, then you might have a &lt;a href="https://wordpress.org/support/article/optimization-caching/#caching-plugins"&gt;caching plugin&lt;/a&gt; handling it. But, in general, a high performance WordPress site will have one.&lt;/p&gt;

&lt;p&gt;The reason a WordPress host wants to use page caching is to reduce the number of requests that make it to PHP. PHP is a lot slower than serving a page using a page cache. It's also a lot more taxing on the server.&lt;/p&gt;

&lt;p&gt;This last bit is the important thing to keep in mind. A page cache can serve thousands of requests per minute without your server breaking a sweat. But because of its resource needs, PHP can only handle a fraction of that, even if you have a massive server.&lt;/p&gt;

&lt;p&gt;So if these thousand of requests handled by the page cache went to PHP instead, it would take down the server. Which is what you often see if someone without a page cache receives a sudden burst of traffic. The site becomes unavailable.&lt;/p&gt;

&lt;p&gt;But not all WordPress pages can use a page cache. For example, you can't cache the WordPress login page or the admin. PHP has to be the one sending the response for these pages.&lt;/p&gt;

&lt;h1&gt;It's all about the cart&lt;/h1&gt;

&lt;p&gt;This brings us to WooCommerce. The architecture for hosting a WooCommerce site looks exactly the same as the one for hosting a WordPress site. You have a page cache, PHP, a database.&lt;/p&gt;

&lt;p&gt;The problem is the pages that a page cache can't cache. More specifically, a certain part of the page: the cart. You can't cache a cart because it's unique to each visitor. And since the cart appears on every page, it means that you can't cache a page once a cart isn't empty.&lt;/p&gt;

&lt;p&gt;This is why I mentioned the scenario about request suddenly going from the page cache to PHP. That's exactly what happens when you add an item to a cart. The requests go from being served from the page cache to being served by PHP because the cart isn't empty.&lt;/p&gt;

&lt;p&gt;Now, this isn't a big deal if you have a few people adding items to cart.&lt;/p&gt;

&lt;p&gt;But what happens if there's a sale? Well, you now have hundreds of people with items in their cart. Each cart can't be cached so your number of PHP requests explodes takes down the server.&lt;/p&gt;

&lt;p&gt;If you're using a specialized WooCommerce host, they mitigate this scenario by giving you a larger server than you'd have with a regular WordPress site. This is one reason they charge more. That said, this only works as long as you don't have any huge sales spikes.&lt;/p&gt;

&lt;p&gt;If your WooCommerce site is more of the type to have those, you're in a tough position. Either you’re paying for those potential spikes all the time. Or you're not, and your site might go down during the most critical time in your business.&lt;/p&gt;

&lt;h1&gt;How does serverless tackle this problem?&lt;/h1&gt;

&lt;p&gt;So how does a serverless PHP solution like &lt;a href="https://ymirapp.com"&gt;Ymir&lt;/a&gt; fix this issue?&lt;/p&gt;

&lt;p&gt;It all comes down to the very nature of serverless computing. Now, the name serverless is misleading. There are still servers somewhere. You just don't have to worry about them.&lt;/p&gt;

&lt;p&gt;In theory, this isn't very different from a managed WordPress host either. That said, there's one big difference versus a managed WordPress host: it's that you don't have to worry about the power of your server either.&lt;/p&gt;

&lt;p&gt;With serverless, each time you make a call to PHP, a small server gets created to process it. When PHP finishes processing the request, the server gets shut down unless there's another request to process. This is all done automatically for you.&lt;/p&gt;

&lt;p&gt;Now, if you're receiving a high volume of requests, serverless computing will create as many servers as necessary to process them. This scaling happens automatically in seconds, and there aren't any limits to how many of these PHP servers you can have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NyNeT7Nl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/03/WordPress-serverless-architecture-1024x213.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NyNeT7Nl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/03/WordPress-serverless-architecture-1024x213.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above is a simplified diagram of our serverless WordPress architecture. It's very similar to our earlier architecture, but now we don't have one server. Instead, we have an infinite pool of potential PHP servers to handle our traffic.&lt;/p&gt;

&lt;p&gt;It's easy to see how this architecture can help mitigate the cart issue when hosting a WooCommerce site. Since there's no more "server", you're no longer limited by the number of requests PHP can handle. So if your WooCommerce site sees a spike in sales, your infrastructure will scale to handle the traffic.&lt;/p&gt;

&lt;p&gt;Once that spike ends, everything scales back down. So you're not stuck paying for a large server to handle those potential spikes or stressed wondering if your site will go down during a sale. It's a weight off your shoulders and wallet!&lt;/p&gt;

&lt;h1&gt;Can't you get a host that autoscales?&lt;/h1&gt;

&lt;p&gt;Container orchestration technologies such as &lt;a href="https://en.wikipedia.org/wiki/Kubernetes"&gt;Kubernetes&lt;/a&gt; are powerful platforms that offer similar infinite scaling potential. Some hosts have already started relying on it to scale their internal infrastructure. So you might be wonder how that technology compares.&lt;/p&gt;

&lt;p&gt;The problem is that scaling with Kubernetes is a lot slower than with serverless. It relies on a cluster autoscaler to determine when to scale and add nodes. Since the autoscaler relies on resource signals to determine when to scale, this creates a lag in the scaling.&lt;/p&gt;

&lt;p&gt;That's why it takes, in the best-case scenario, about a minute for Kubernetes to spin up a new node. At worst, several minutes.&lt;/p&gt;

&lt;p&gt;By the time that node comes up, it's often too late. Your WooCommerce site has been unresponsive for a while. You're also constantly trying to play catch up trying to match your infrastructure needs to the current demand.&lt;/p&gt;

&lt;p&gt;AWS Lambda scales the moment a request cannot get processed. There's still a &lt;a href="https://en.wikipedia.org/wiki/Cold_start_(computing)"&gt;cold start&lt;/a&gt; to the new Lambda which creates a bit of lag. That said, it's a few seconds compared to minutes.&lt;/p&gt;

&lt;h1&gt;What about the database?&lt;/h1&gt;

&lt;p&gt;You probably noticed that we haven't talked about the database yet. Unfortunately, while serverless databases exist, their cost and performance isn't quite there yet. So while serverless will solve the problem with carts overwhelming your PHP server, the same isn't true for databases. (At least, not yet!)&lt;/p&gt;

&lt;p&gt;This means that we still have a similar problem as before. If you're not careful, the PHP servers that serverless computing creates can overwhelm your database server. Does that mean that we're out of luck?&lt;/p&gt;

&lt;p&gt;No, of course not! This database problem is something hosting companies have to contend with as well. But the larger positive is that database servers are a lot more performant than a PHP server and harder to take down.&lt;/p&gt;

&lt;p&gt;How hard are they to take down? Well, look no further than this &lt;a href="https://twitter.com/twigpress/status/1375488641166934019"&gt;Twitter thread&lt;/a&gt; discussing load testing done on WooCommerce running on &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt;. The real kicker is this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SdkTaC54--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/03/tweet-load-balancing-1024x543.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SdkTaC54--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ymirapp.com/uploads/2021/03/tweet-load-balancing-1024x543.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A $13/month &lt;a href="https://aws.amazon.com/rds"&gt;RDS&lt;/a&gt; database was able to handle at least 300 concurrent customers checking out at the same time with no object cache. You'd need a massive server with a lot of PHP workers to handle that much traffic. WordPress hosts will charge several hundred dollars per month for such a server.&lt;/p&gt;

&lt;p&gt;But with serverless, you have access to it on demand, and you don't have to pay for anything when there's no traffic. So not only are you capable of handling massive traffic spikes, it'll also cost you less to do so. (It's not completly free because you still have the database to pay for.) It's a win-win scenario!&lt;/p&gt;

&lt;h1&gt;A new beginning&lt;/h1&gt;

&lt;p&gt;Serverless is still in its early days. But seeing how it trivializes hosting server intensive platforms like WooCommerce is really mind-blowing.&lt;/p&gt;

&lt;p&gt;If you run a WooCommerce store, you shouldn't worry about your server whenever you have a spike in sales. You should have confidence that it can handle anything that you can throw at it.&lt;/p&gt;

&lt;p&gt;With serverless, we're closer to this than ever before.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>woocommerce</category>
      <category>serverless</category>
      <category>hosting</category>
    </item>
    <item>
      <title>How to test Stripe Checkout and Customer portal with Laravel Dusk</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Thu, 04 Mar 2021 06:44:13 +0000</pubDate>
      <link>https://forem.com/carlalexander/how-to-test-stripe-checkout-and-customer-portal-with-laravel-dusk-2o1j</link>
      <guid>https://forem.com/carlalexander/how-to-test-stripe-checkout-and-customer-portal-with-laravel-dusk-2o1j</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZqNoby3s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzzfu9sonnna6jpa6mpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZqNoby3s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzzfu9sonnna6jpa6mpe.png" alt="How to test Stripe Checkout and Customer portal with Laravel Dusk"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently, I added billing to &lt;a href="https://ymirapp.com"&gt;Ymir&lt;/a&gt;. It's a &lt;a href="https://laravel.com"&gt;Laravel&lt;/a&gt; application, and adding payment functionality to it was a breeze with &lt;a href="https://laravel.com/docs/master/billing"&gt;Laravel Cashier&lt;/a&gt;. Cashier supports different payment gateways, but it was originally designed for Stripe. It's also what I'm using with Ymir.&lt;/p&gt;

&lt;p&gt;Over the last year or so, Stripe has released two products to ease the engineering burden dealing with payments. First, there's &lt;a href="https://stripe.com/docs/payments/checkout"&gt;Checkout&lt;/a&gt;, which lets you not worry about building a payment page. Second, there's the &lt;a href="https://stripe.com/docs/billing/subscriptions/customer-portal"&gt;customer portal&lt;/a&gt; which lets people manage their subscription and billing information.&lt;/p&gt;

&lt;p&gt;Laravel Cashier has support for both Checkout and the Customer portal, which means you don't have to code any payment backend. But that doesn't mean that you don't want to test the integration between Stripe and your Laravel application. After all, payment is one of the most important part of your application!&lt;/p&gt;

&lt;p&gt;To do that, you'll want to use &lt;a href="https://laravel.com/docs/master/billing"&gt;Laravel Dusk&lt;/a&gt; to do browser testing for your application. If you're not doing Browser testing already, you should consider looking into it. You could use another framework, but Dusk is the standard in the Laravel ecosystem.&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting webhooks to work during tests
&lt;/h1&gt;

&lt;p&gt;Doing browser testing with Stripe Checkout and the Customer portal comes with certain challenges. The biggest one is that these products communicate with your application via &lt;a href="https://en.wikipedia.org/wiki/Webhook"&gt;webhooks&lt;/a&gt;. When developing your Laravel application, you use the &lt;a href="https://stripe.com/docs/stripe-cli"&gt;Stripe CLI&lt;/a&gt; to forward webhooks to it.&lt;/p&gt;

&lt;p&gt;We'll also need to do that when we run our browser tests. So an important aspect of using Laravel Dusk is having the Stripe CLI to forward our webhooks during the tests. We need to automate that as part of the test suite.&lt;/p&gt;

&lt;p&gt;A simple way to automate the use of the Stripe CLI is to run it as a background process during our tests. We can use the &lt;a href="https://phpunit.de/"&gt;PHPUnit&lt;/a&gt; &lt;code&gt;setUp&lt;/code&gt; and &lt;code&gt;tearDown&lt;/code&gt; methods to do it. Whenever we need the Stripe CLI, the &lt;code&gt;setUp&lt;/code&gt; method starts the process and then stops it in the &lt;code&gt;tearDown&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Process\Process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tests\DuskTestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DuskTestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * @var Process
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'stripe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'listen'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--api-key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'STRIPE_SECRET'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'--forward-to'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%s/stripe/webhook'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'APP_URL'&lt;/span&gt;&lt;span class="p"&gt;))]);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stop&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;Above is the base test case class you can use to scaffold the Stripe CLI. It uses the &lt;a href="https://symfony.com/doc/current/components/process.html"&gt;Symfony Process component&lt;/a&gt; to run the process in the background. It runs until the &lt;code&gt;stop&lt;/code&gt; method gets called in the &lt;code&gt;tearDown&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;The code expects two environment variables to be present. First, there's the &lt;code&gt;STRIPE_SECRET&lt;/code&gt; environment variable containing your Stripe secret key. This allows the Stripe CLI to login without you having to enter your email and password. (You can read more about it &lt;a href="https://stripe.com/docs/cli/api_keys"&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Second, there's the &lt;code&gt;APP_URL&lt;/code&gt; environment variable. We use it to generate the URL to pass to the &lt;code&gt;--forward-to&lt;/code&gt; option. The &lt;code&gt;--forward-to&lt;/code&gt; URL is where the Stripe CLI will forward webhook requests from Stripe. The default route with Laravel Cashier is &lt;code&gt;/stripe/webhook&lt;/code&gt; so we just prepend the &lt;code&gt;APP_URL&lt;/code&gt; environment variable to it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cleaning up the customer after a test
&lt;/h1&gt;

&lt;p&gt;Another challenge with testing the Stripe Checkout and Customer portal is cleaning up after the tests. You don't want to end up with hundreds of customers in your Stripe test environment. So you need to set up your test classes so that they can clean up any customer they might have created.&lt;/p&gt;

&lt;p&gt;A simple way to achieve this is by storing the created customer in your test class. Once the test completes, you delete the customer in Stripe. Here's one way you could do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Model\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stripe\Customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stripe\Exception\InvalidRequestException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stripe\Stripe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Process\Process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tests\DuskTestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DuskTestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * @var Process
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @var User
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'STRIPE_SECRET'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'stripe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'listen'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--api-key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--forward-to'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%s/stripe/webhook'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'APP_URL'&lt;/span&gt;&lt;span class="p"&gt;))]);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;setApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

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

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stripe_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InvalidRequestException&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is the updated code sample from earlier. We added a few more things to take care of the customer cleanup. If you'd rather do this clean up inside each of your tests, you don't have to add any of this code.&lt;/p&gt;

&lt;p&gt;First, there's a new &lt;code&gt;user&lt;/code&gt; property. It'll store the &lt;code&gt;User&lt;/code&gt; model object that has the Stripe customer ID that we want to clean up. (If you're using another model to store the Stripe customer ID, you should store that model instead.) During your tests, if you create a &lt;code&gt;User&lt;/code&gt; model, you want to assign it to that property instead of creating a variable for it.&lt;/p&gt;

&lt;p&gt;Next, there's a minor change in the &lt;code&gt;setUp&lt;/code&gt; method. We added code to initialize the Stripe PHP SDK by calling the &lt;code&gt;setApiKey&lt;/code&gt; method. We pass it the API secret key which Laravel Cashier expects to be in &lt;code&gt;STRIPE_SECRET&lt;/code&gt; environment variable by default.&lt;/p&gt;

&lt;p&gt;After that, in the &lt;code&gt;tearDown&lt;/code&gt; method, we added code to delete the customer in Stripe. We start by checking if the &lt;code&gt;user&lt;/code&gt; property has a &lt;code&gt;User&lt;/code&gt; object stored in it. If it does, we call the code to delete the customer in Stripe.&lt;/p&gt;

&lt;p&gt;To delete the customer,  we create an instance of the Stripe &lt;code&gt;Customer&lt;/code&gt; class using the ID stored in the &lt;code&gt;User&lt;/code&gt; model. We then call the &lt;code&gt;delete&lt;/code&gt; method. We put that code in a simple &lt;a href="https://en.wikipedia.org/wiki/Exception_handling"&gt;try-catch&lt;/a&gt; block. This is so we can discard errors if the customer didn't exist in Stripe.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating page classes for Stripe
&lt;/h1&gt;

&lt;p&gt;This wraps up the base test class. Next, we want to look at what we need to create &lt;a href="https://laravel.com/docs/master/dusk#pages"&gt;page classes&lt;/a&gt;. Page classes are a good way to group actions  you want to perform on specific web pages. &lt;/p&gt;

&lt;p&gt;With Stripe, we need a page class for both the checkout page and the customer portal. Both will share some similar elements. To begin, let's create the checkout page class. (You can do this with the &lt;code&gt;php artisan dusk:page&lt;/code&gt; command.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser\Pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Dusk\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Checkout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Get the URL for the page.
     */&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;url&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Assert that the browser is on the page.
     */&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertPathIs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Get the element shortcuts for the page.
     */&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;elements&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;'@element'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#selector'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is what you'd get if you ran the command php artisan dusk:page Checkout. The command creates a Checkout class with some methods. We only care about the url and assert methods for this article so we won't discuss the elements method.&lt;/p&gt;

&lt;h1&gt;
  
  
  Asserting external URLs
&lt;/h1&gt;

&lt;p&gt;Now, by default, Dusk lets you test pages that aren't part of your Laravel application. You can just replace the value returned by &lt;code&gt;url&lt;/code&gt; method by a full URL. You then use the method &lt;code&gt;assertUrlIs&lt;/code&gt; to check the URL in the &lt;code&gt;assert&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;The issue is that the Stripe URLs are dynamic. So the &lt;code&gt;assertUrlIs&lt;/code&gt; method won't work to validate that we're on the right page. To fix this, we'll need to create a &lt;a href="https://laravel.com/docs/master/dusk#browser-macros"&gt;macro&lt;/a&gt; to add a custom assertion method in the &lt;code&gt;Browser&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Dusk\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DuskServiceProvider&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ServiceProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Register Dusk's browser macros.
     */&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;boot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Browser&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;macro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'assertUrlBeginsWith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;preg_quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="nv"&gt;$segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parse_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCurrentURL&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

            &lt;span class="nv"&gt;$currentUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'%s://%s%s%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'scheme'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="nv"&gt;$segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'host'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="nc"&gt;Arr&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'port'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'port'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nc"&gt;Arr&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;PHPUnit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;$currentUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegularExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/^'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s2"&gt;"Actual URL [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCurrentURL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] does not begin with the expected URL [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is the modified &lt;code&gt;DuskServiceProvider&lt;/code&gt; class with the macro for the &lt;code&gt;assertUrlBeginsWith&lt;/code&gt; method. It's a method that checks if the URL starts a specific way, but it doesn't have to be identical. &lt;/p&gt;

&lt;p&gt;The code itself is nearly identical to the &lt;code&gt;assertUrlIs&lt;/code&gt; method. The only change was to remove the &lt;code&gt;$&lt;/code&gt; in the &lt;a href="https://carlalexander.ca/beginners-guide-regular-expressions/"&gt;regular expression&lt;/a&gt;. This changes the regular expression from requiring an exact match to just having the string begin with a specific URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modifying the assert and url methods
&lt;/h2&gt;

&lt;p&gt;With our macro created, we can go back to our &lt;code&gt;Checkout&lt;/code&gt; class. We're going to fill in the &lt;code&gt;assert&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt; methods. After the changes, our &lt;code&gt;Checkout&lt;/code&gt; class will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser\Pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Dusk\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Checkout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Assert that the browser is on the page.
     */&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertUrlBeginsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Get the URL for the page.
     */&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;url&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="s1"&gt;'https://checkout.stripe.com/pay'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;url&lt;/code&gt; method returns &lt;code&gt;https://checkout.stripe.com/pay&lt;/code&gt; which is the URL that all Stripe Checkout links start with. The &lt;code&gt;assert&lt;/code&gt; method passes that URL to our macroed &lt;code&gt;assertUrlBeginsWith&lt;/code&gt; method to check the page URL starts with the URL. We then do the same thing for the Stripe customer portal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser\Pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Dusk\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerPortal&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Get the URL for the page.
     */&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;url&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="s1"&gt;'https://billing.stripe.com/session'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Assert that the browser is on the page.
     */&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertUrlBeginsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CustomerPortal&lt;/code&gt; class is also nearly identical to the &lt;code&gt;Checkout&lt;/code&gt; class. We just changed the URL returned by the &lt;code&gt;url&lt;/code&gt; method. The customer portal URL starts with &lt;code&gt;https://billing.stripe.com/session&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing Stripe subscription
&lt;/h1&gt;

&lt;p&gt;Now, we have all we need to write our browser tests. These tests are going to vary from one Laravel application to another. But we can go over a small hypothetical one that goes over some common operations you might want to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Process\Process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tests\DuskTestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DuskTestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&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;testSubscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;browse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

            &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loginAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;goToBilling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Checkout&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;enterValidCreditCard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;enterNameOnCreditCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;enterAddress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomerPortal&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

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

            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is the updated &lt;code&gt;StripeTest&lt;/code&gt; class with our sample test called &lt;code&gt;testSubscribe&lt;/code&gt;. It tests that a subscription gets created in our Laravel application when we enter a valid credit card in the Stripe Checkout. Once subscribed, we should get redirected to the customer portal. (You can do this by setting the checkout return URL to the &lt;a href="https://laravel.com/docs/master/billing#billing-portal"&gt;application URL that redirects to the customer portal&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Walking through the test code, it starts by creating a &lt;code&gt;User&lt;/code&gt; using its &lt;a href="https://laravel.com/docs/master/database-testing#defining-model-factories"&gt;model factory&lt;/a&gt;. We then do a quick assertion to check that the user has no subscriptions. You can skip this if you prefer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser actions
&lt;/h2&gt;

&lt;p&gt;After that, we start our test by performing browser actions through the &lt;code&gt;Browser&lt;/code&gt; class. First, we need to ensure that the browser is logged in by using the &lt;code&gt;loginAs&lt;/code&gt; method. &lt;/p&gt;

&lt;p&gt;Then we have &lt;code&gt;goToBilling&lt;/code&gt; method. This is a hypothetical method that represents whatever actions you'd need the browser to do to get to and click the Stripe checkout link in your Laravel application. Clicking that link should then send us to the Stripe checkout page.&lt;/p&gt;

&lt;p&gt;We verify this by using the &lt;code&gt;on&lt;/code&gt; method with the &lt;code&gt;Checkout&lt;/code&gt; class we created. Because of the changes we did earlier, Dusk should correctly detect that the browser is on a Stripe checkout page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkout class methods
&lt;/h2&gt;

&lt;p&gt;Once we know that we're on the Stripe checkout page, we need to enter the payment information. We do that with some &lt;a href="https://laravel.com/docs/master/dusk#page-methods"&gt;page methods&lt;/a&gt;. For clarity, we did one page method per form section. But if you'd prefer one for the entire process, that's fine too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser\Pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Dusk\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Checkout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Enter an address.
     */&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;enterAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#billingCountry'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CA'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'billingPostalCode'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'H0H0H0'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Enter the name on the credit card.
     */&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;enterNameOnCreditCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#billingName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Enter a valid credit card.
     */&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;enterValidCreditCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#cardNumber'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'4242424242424242'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#cardExpiry'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0129'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#cardCvc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'111'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Press the subscribe button.
     */&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;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Subscribe'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20000&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;Above is the updated &lt;code&gt;Checkout&lt;/code&gt; class with the new page methods. Most of the values for the form are hardcoded. (Fun fact: &lt;code&gt;H0H 0H0&lt;/code&gt; is the &lt;a href="https://en.wikipedia.org/wiki/Postal_codes_in_Canada#Santa_Claus"&gt;Santa Claus' Canadian postal code&lt;/a&gt;.) If you'd rather pass them as arguments, that's ok as well. You're really free to build out these methods in the way that makes the most sense to you.&lt;/p&gt;

&lt;p&gt;It's worth noting that you can't change the &lt;code&gt;4242424242424242&lt;/code&gt; credit card number. It's the valid Visa credit card number that Stripe will accept in test mode. (You can view all the different card numbers in the &lt;a href="https://stripe.com/docs/testing#cards"&gt;Stripe documentation&lt;/a&gt;.) The CVC number can be anything, and the expiry date only needs to be a future date.&lt;/p&gt;

&lt;p&gt;Another thing that might be unusual is the call to the &lt;code&gt;pause&lt;/code&gt; method after pressing the subscribe button. You don't have to put the pause there, but a pause is necessary. You need to give Stripe the time to process the request, but also send over the subscription information to your Laravel application via the Stripe CLI. &lt;/p&gt;

&lt;p&gt;20 seconds is enough time in my experience. I tried lower values, but sometimes the tests would fail. So be aware if you choose to put a smaller pause time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirecting to the customer portal
&lt;/h2&gt;

&lt;p&gt;After you subscribe, you’ll notice that our test expects Stripe to redirect us to the customer portal. To do that, you need to set up a route in your application that redirects to the customer portal. The &lt;a href="https://laravel.com/docs/master/billing#billing-portal"&gt;Cashier documentation&lt;/a&gt; shows how you can do that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/billing-portal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;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;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;redirectToBillingPortal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'billing-portal'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You then want to use that route when creating your Stripe checkout link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;newSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price_xxx'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'success_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'billing-portal'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'cancel_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your-cancel-route'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verifying the subscription
&lt;/h2&gt;

&lt;p&gt;Once we're done testing with the browser, we want to check that our Laravel app received the changes from the Stripe API. To do that, we need an up-to-date version of our user. So we need to reload the model using the &lt;code&gt;refresh&lt;/code&gt; method. &lt;/p&gt;

&lt;p&gt;After that, we need to get the &lt;code&gt;Subscription&lt;/code&gt; model for the user. If all worked well, you should have one. We then check that the subscription is active by asserting that the &lt;code&gt;active&lt;/code&gt; method returns &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Test cancelling an active subscription
&lt;/h1&gt;

&lt;p&gt;Lastly, we’ll look at a test using the customer portal. There are a few tests you can do there, but the one we’ll look at is cancelling an existing subscription. Below is the updated &lt;code&gt;StripeTest&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Process\Process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tests\DuskTestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DuskTestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&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;testCancelSubscription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;browse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;newSubscription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                       &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pm_card_visa'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

            &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loginAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;goToBilling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomerPortalPage&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cancelSubscription&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

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

            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test starts the same as the one to test the subscription checkout flow. We create a user using the model factory. &lt;/p&gt;

&lt;p&gt;But after that, we create a Stripe subscription using the &lt;code&gt;newSubscription&lt;/code&gt; method followed by the &lt;code&gt;create&lt;/code&gt; method. This will create a subscription in Stripe using the given payment method ID. &lt;code&gt;pm_card_visa&lt;/code&gt; is the payment method ID of a Visa credit card in the Stripe test environment.&lt;/p&gt;

&lt;p&gt;Once the Stripe subscription created, we do a quick sanity check to check that the subscription isn’t in a  cancelled state. Once that’s done, we can start performing the browser actions. There’s only one specific one for the customer portal for this test. It’s clicking on the cancel subscription button.&lt;/p&gt;

&lt;p&gt;But first, we need to navigate to the customer portal. We’re going to use the &lt;code&gt;goToBilling&lt;/code&gt; method again for that. Then we ensure that we're on the customer portal using the &lt;code&gt;on&lt;/code&gt; method. Then we can cancel the subscription.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\Browser\Pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Dusk\Browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerPortal&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Cancel an active subscription.
     */&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;cancelSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Browser&lt;/span&gt; &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;clickLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Cancel plan'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Cancel plan'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&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;Above is the &lt;code&gt;cancelSubscription&lt;/code&gt; method. On the Stripe customer portal, you need to click on “Cancel plan” twice to cancel your subscription. The first time you’re clicking on a link and the second time it’s a button. A pause is necessary between both clicks to allow the site to react to the first press. &lt;/p&gt;

&lt;p&gt;The second pause is there for the same reason as the one for testing the subscription. It allows Stripe to communicate back to the Laravel application via the Stripe CLI.&lt;/p&gt;

&lt;p&gt;The test also wraps up similarly as the subscription one. We reload the &lt;code&gt;User&lt;/code&gt; model to load the changes from the Stripe. We then do an assertion to ensure that the subscription is in a cancelled state.&lt;/p&gt;

&lt;h1&gt;
  
  
  A good foundation
&lt;/h1&gt;

&lt;p&gt;So with this, you have enough to test a subscription with Stripe checkout and a cancellation using the customer portal. There are still a lot of other scenarios you might want (and should!) to test. But this should give you the foundation you need to make writing those tests a breeze!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>acceptancetesting</category>
      <category>browsertesting</category>
      <category>laravel</category>
    </item>
    <item>
      <title>What is software complexity and how can you manage it?</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Wed, 20 Jan 2021 04:19:07 +0000</pubDate>
      <link>https://forem.com/carlalexander/what-is-software-complexity-and-how-can-you-manage-it-39kd</link>
      <guid>https://forem.com/carlalexander/what-is-software-complexity-and-how-can-you-manage-it-39kd</guid>
      <description>&lt;p&gt;As developers, we spend a lot of time writing code. But we spend even more time maintaining that code. How often do we go back find that that code has become this tangled mess that we almost can’t understand? It's probably more often than we want to admit!&lt;/p&gt;

&lt;p&gt;We wonder, "How did this happen? How did this code get so messy?" Well, the most likely culprit is &lt;a href="https://en.wikipedia.org/wiki/Programming_complexity"&gt;software complexity&lt;/a&gt;. Our code became so complex that it became hard to know what it did.&lt;/p&gt;

&lt;p&gt;Now, software complexity isn't a topic that developers are often familiar with when they start coding. We have other things to worry about. We're trying to learn a new programming language or a new framework.&lt;/p&gt;

&lt;p&gt;We don't stop and think that software complexity could be making that job harder for us. But it is doing precisely that. We're creating code that works, but that's also hard to maintain and understand. That's why we often come back and ask ourselves, "What was I thinking!? This makes no sense."&lt;/p&gt;

&lt;p&gt;That's why learning about software complexity is important. It'll help you increase the quality of your code so that these situations don't happen as often. And this also has the added benefit of making your code less prone to bugs. (That's a good thing even if &lt;a href="https://carlalexander.ca/how-debugging-make-better-developer/"&gt;debugging is a great learning tool&lt;/a&gt;!)&lt;/p&gt;

&lt;h1&gt;
  
  
  What is software complexity?
&lt;/h1&gt;

&lt;p&gt;Let's start by going of software complexity as a concept. Software complexity is a way to describe a specific set of characteristics of your code. These characteristics all focus on how your code interacts with other pieces of code.&lt;/p&gt;

&lt;p&gt;The measurement of these characteristics is what determines the complexity of your code. It's a lot like a software quality grade for your code. The problem is that there are several ways to measure these characteristics.&lt;/p&gt;

&lt;p&gt;We're not going to look at all these different measurements. (It wouldn't be super useful to do so anyway.) Instead, we're going to focus on two specific ones: cyclomatic complexity and NPath. These two measurements are more than enough for you to evaluate the complexity of your code.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cyclomatic complexity
&lt;/h1&gt;

&lt;p&gt;If we had to pick one metric to use for measuring complexity, it would be &lt;a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity"&gt;cyclomatic complexity&lt;/a&gt;. It's without question the better-known complexity measurement method. In fact, it's common for developers often use the terms "software complexity" and "cyclomatic complexity" interchangeably.&lt;/p&gt;

&lt;p&gt;Cyclomatic complexity measures the number of "linearly independent paths" through a piece of code. A linearly independent path is a fancy way of saying a "unique path where we count loops only once". But this is still a bit confusing, so let's look at a small example using this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;insert_default_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$mixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pretty straightforward function. The &lt;code&gt;insert_default_value&lt;/code&gt; has one parameter called &lt;code&gt;mixed&lt;/code&gt;. We check if it's &lt;a href="https://secure.php.net/manual/en/function.empty.php"&gt;empty&lt;/a&gt; and if it is, we set the string &lt;code&gt;value&lt;/code&gt; as its value.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to calculate cyclomatic complexity
&lt;/h2&gt;

&lt;p&gt;You calculate cyclomatic complexity using a &lt;a href="https://en.wikipedia.org/wiki/Control_flow_graph"&gt;control flow graph&lt;/a&gt;. This is a &lt;a href="https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)"&gt;graph&lt;/a&gt; that represents all the possible paths through your code. If we converted our code into a control flow graph, it would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lytFjvu_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d3nw5tt5i784nr.cloudfront.net/uploads/2018/02/control-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lytFjvu_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d3nw5tt5i784nr.cloudfront.net/uploads/2018/02/control-graph.png" alt="" width="400" class="aligncenter size-full wp-image-2226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our graph has four &lt;a href="https://en.wikipedia.org/wiki/Vertex_(graph_theory)"&gt;nodes&lt;/a&gt;. The top and bottom ones are for the beginning and end of the &lt;code&gt;insert_default_value&lt;/code&gt;. The two other nodes are for the states when &lt;code&gt;empty&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; and when it returns &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our graph also has four edges. Those are the arrows that connect our four nodes. To calculate the cyclomatic complexity of our code, we use these two numbers in this formula: &lt;code&gt;M = E − N + 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;M&lt;/code&gt; is the calculated complexity of our code. (Not sure why it's an &lt;code&gt;M&lt;/code&gt; and not a &lt;code&gt;C&lt;/code&gt;.) &lt;code&gt;E&lt;/code&gt; is the number of edges and &lt;code&gt;N&lt;/code&gt; is the number of nodes. The &lt;code&gt;2&lt;/code&gt; comes from a simplification of the regular cyclomatic complexity equation. (It's because we're always evaluating a single function or method.)&lt;/p&gt;

&lt;p&gt;So what happens if we plug our previous numbers into our formula? Well, we get a cyclomatic complexity of &lt;code&gt;M = 4 − 4 + 2 = 2&lt;/code&gt; for the &lt;code&gt;insert_default_value&lt;/code&gt; function. This means that there are two "linearly independent paths" through our function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oQQo0-J1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d3nw5tt5i784nr.cloudfront.net/uploads/2018/02/control-graph-paths.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oQQo0-J1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d3nw5tt5i784nr.cloudfront.net/uploads/2018/02/control-graph-paths.png" alt="" width="400" class="aligncenter size-full wp-image-2228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is pretty easy to see in our updated graph above. One path was for if our &lt;code&gt;if&lt;/code&gt; condition was &lt;code&gt;true&lt;/code&gt; and the other was for if it wasn't. We represented these two paths with red arrows on each side of the control flow graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative way to calculate it
&lt;/h2&gt;

&lt;p&gt;Now looking at what we just did, it's pretty clear that cyclomatic complexity isn't that user-friendly. Most of us don't have mathematics degrees. And we sure don't want to draw graphs and fill values in formulas while we're coding!&lt;/p&gt;

&lt;p&gt;So what can we do instead? Well, there's a way to calculate the cyclomatic complexity without having to draw a graph. You want to count every &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt; and &lt;code&gt;case&lt;/code&gt; statements in your code as well as the entry to your function or method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;insert_default_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;               &lt;span class="c1"&gt;// 2&lt;/span&gt;
        &lt;span class="nv"&gt;$mixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$mixed&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;It's worth noting that with &lt;code&gt;if&lt;/code&gt; statements you have to count each condition in it. So, if you had two conditions inside your &lt;code&gt;if&lt;/code&gt; statement, you'd have to count both. Here's an example of that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;insert_default_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;is_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// 2,3&lt;/span&gt;
        &lt;span class="nv"&gt;$mixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$mixed&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;As you can see, we added a &lt;a href="https://secure.php.net/manual/en/function.is-string.php"&gt;&lt;code&gt;is_string&lt;/code&gt;&lt;/a&gt; before the empty check in our &lt;code&gt;if&lt;/code&gt; statement. This means that we should count our &lt;code&gt;if&lt;/code&gt; statement twice. This brings the cyclomatic complexity of our function to 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's a good cyclomatic complexity value?
&lt;/h2&gt;

&lt;p&gt;Alright, so you now have a better idea of what cyclomatic complexity is and how to calculate it. But this doesn't answer everything. You're still asking yourself, "How do I know if my function is too complex? What cyclomatic complexity value will tell me that?"&lt;/p&gt;

&lt;p&gt;As a general rule, if you have a cyclomatic complexity value between 1 and 4, your code isn't that complex. We don't tend to think of code within that range as complex either. Most small functions of a dozen lines of code or less fit within that range. &lt;/p&gt;

&lt;p&gt;A cyclomatic complexity value between 5 and 7 is when things start unravelling. When your code is in that range, its complexity becomes noticeable. You can already start looking at ways to reduce complexity. (We'll see what you can do to reduce complexity later in the article.)&lt;/p&gt;

&lt;p&gt;But what if your code's cyclomatic complexity is even higher? Well at that point, you're now well into the "complex code" territory. A value between 8 and 10 is often the upper limit before code analysis tools will start warning you. So, if your code has a cyclomatic complexity value over 10, you shouldn't hesitate to try and fix it right away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues with cyclomatic complexity
&lt;/h2&gt;

&lt;p&gt;We already discussed the role of mathematics in cyclomatic complexity. If you love math, that's great. But it's not that intuitive if you're not familiar with mathematical graphs. &lt;/p&gt;

&lt;p&gt;That said, there are two conceptual problems with cyclomatic complexity. Unlike the issue with mathematics, these two issues are quite important. That's because they affect the usefulness of cyclomatic complexity as a metric.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not every statement is equal
&lt;/h3&gt;

&lt;p&gt;The first one is that cyclomatic complexity considers all &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt; and &lt;code&gt;case&lt;/code&gt; statements as identical. But, in practice, this isn't the case. For example, let's look at a &lt;code&gt;for&lt;/code&gt; loop compared to an &lt;code&gt;if&lt;/code&gt; condition.&lt;/p&gt;

&lt;p&gt;With a &lt;code&gt;for&lt;/code&gt; loop, the path through it is always the same. It doesn't matter if you loop through it once or 10,000 times. It's always the same code that gets processed over and over.&lt;/p&gt;

&lt;p&gt;This isn't the case with an &lt;code&gt;if&lt;/code&gt; condition. It isn't linear like a &lt;code&gt;for&lt;/code&gt; loop. (It's more like a fork in a road.) The path through your code will change depending on whether that &lt;code&gt;if&lt;/code&gt; condition is true or false.&lt;/p&gt;

&lt;p&gt;These alternative paths through your code have a larger effect on its complexity than a &lt;code&gt;for&lt;/code&gt; loop. All the more so if your &lt;code&gt;if&lt;/code&gt; conditions contain a lot of code. In those situations, the difference between your &lt;code&gt;if&lt;/code&gt; condition being &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; can be significant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nesting
&lt;/h3&gt;

&lt;p&gt;The other problem with cyclomatic complexity is that it doesn't account for nesting. For example, let's imagine that you had code with three nested &lt;code&gt;for&lt;/code&gt; loops. Well, cyclomatic complexity considers them as complex as if they were one after the other. &lt;/p&gt;

&lt;p&gt;But we've all seen nested &lt;code&gt;for&lt;/code&gt; loops before. They don't feel as complex as a linear succession of &lt;code&gt;for&lt;/code&gt; loops. In fact, they more often than not feel more complex.&lt;/p&gt;

&lt;p&gt;This is due in part to the &lt;a href="https://en.wikipedia.org/wiki/Cognitive_complexity#In_computer_science"&gt;cognitive complexity&lt;/a&gt; of nested code. Nested code is harder to understand. It's something that a complexity measurement should take into consideration.&lt;/p&gt;

&lt;p&gt;After all, we're the ones who are going to debug this code. We should be able to understand what it does. If we can't, it doesn't matter whether it's complex or not.&lt;/p&gt;

&lt;h1&gt;
  
  
  Complex vs complicated
&lt;/h1&gt;

&lt;p&gt;The idea that code feels complex or is harder to understand is worth discussing. That's because there's a term that we use to describe that type code: complicated. It's also common to think that complex and complicated mean the same thing.&lt;/p&gt;

&lt;p&gt;But that's not quite the case. We use these two terms to describe two different things in our code. The confusion comes from the fact that our code is often both complex and complicated.&lt;/p&gt;

&lt;p&gt;So far, we've only discussed the meaning of complex. When we say that code is complex, we're talking about its level of complexity. It's code that has a cyclomatic complexity value. (Or a high value in another measurement method.) It's also something that's measurable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining complicated code
&lt;/h2&gt;

&lt;p&gt;But, when we say that code is complicated, it doesn't have anything to do with complexity. It has to do with the psychological complexity that we talked about with nesting. It's the answer to the question, "Is your code hard to understand?" &lt;/p&gt;

&lt;p&gt;If the answer is "yes" then it's complicated. Otherwise, it's not complicated. But whatever the answer may be, it's still subjective.&lt;/p&gt;

&lt;p&gt;Code that's complicated for you might not be for someone else. And the opposite is true as well. Code that isn't complicated for you might be complicated for someone else. (Or even your future self!)&lt;/p&gt;

&lt;p&gt;This also means that code that was once complicated can become straightforward. (And vice versa!) If you take the time that you need, you can figure out how complicated code works. At that point, it isn't complicated anymore.&lt;/p&gt;

&lt;p&gt;But that'll never be the case with complex code. That's because, when we say that code is complex, we base that on a measurement. And that measurement will never change as long as that code stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes code complex and complicated?
&lt;/h2&gt;

&lt;p&gt;Now, let's talk about why the two terms get confused. If you think about what makes code complex, it's the number of statements in it. (Well, that's the simple way to look at it.) The more statements there are, the higher the cyclomatic complexity will be.&lt;/p&gt;

&lt;p&gt;But code that has a lot of statements in it isn't just complex. There's also more going on. It's harder to keep track of everything that's going on. (Even more so if a lot of the statements are nested.) &lt;/p&gt;

&lt;p&gt;That's what makes complex code harder to understand. It's also why it's common to think that the two terms mean the same thing. But, as we just saw, that's not the case.&lt;/p&gt;

&lt;p&gt;In fact, your code can be complicated without being complex. For example, using &lt;a href="https://carlalexander.ca/importance-naming-programming/"&gt;poor variable names&lt;/a&gt; is a way to make your code complicated without making it complex. It's also possible for complex code to not be complicated as well.&lt;/p&gt;

&lt;h1&gt;
  
  
  NPATH
&lt;/h1&gt;

&lt;p&gt;So this gives us a better understanding of what complicated code means. Now, we can move on and discuss another way to measure the complexity of a piece of code. We call this measurement method NPATH.&lt;/p&gt;

&lt;p&gt;Unlike cyclomatic complexity, NPATH isn't as well known by developers. There's no Wikipedia page for it. (&lt;em&gt;gasp&lt;/em&gt;) You have to read the &lt;a href="https://dl.acm.org/citation.cfm?id=42379"&gt;paper&lt;/a&gt; on it if you want to learn about it. (Or keep reading this article!)&lt;/p&gt;

&lt;p&gt;The paper explains the shortcomings of cyclomatic complexity. Some of which we saw earlier. It then proposes NPATH as an alternative measurement method.&lt;/p&gt;

&lt;h2&gt;
  
  
  NPATH explained
&lt;/h2&gt;

&lt;p&gt;The essence of NPATH is what the paper calls "acyclic execution path". This is another fancy technical term that sounds complicated. But it's quite simple. It just means "unique path through your code".&lt;/p&gt;

&lt;p&gt;This is something that's pretty easy to visualize with an example. So let's go back to our earlier example with the &lt;code&gt;insert_default_value&lt;/code&gt; function. Here's the code for it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;insert_default_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$mixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how many unique paths are there through the &lt;code&gt;insert_default_value&lt;/code&gt; function? The answer is two. One unique path is when &lt;code&gt;mixed&lt;/code&gt; is empty, and the other is when it's not.&lt;/p&gt;

&lt;p&gt;But that was just the first iteration of our &lt;code&gt;insert_default_value&lt;/code&gt; function. We also updated it to use the &lt;code&gt;is_string&lt;/code&gt; function as well as the empty check. Let's do the same thing for it as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;insert_default_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;is_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mixed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$mixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$mixed&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;With this change, there are now three unique paths through our &lt;code&gt;insert_default_value&lt;/code&gt; function. So adding this condition only added one extra path to it. In case you're wondering, these three paths are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When &lt;code&gt;mixed&lt;/code&gt; isn't a string. (PHP won't continue evaluating the conditional when that happens. You can read more about it &lt;a href="https://carlalexander.ca/mastering-php-conditionals/"&gt;here&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;mixed&lt;/code&gt; is a string, but it's empty.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;mixed&lt;/code&gt; is a string, but it's not empty.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Adding more complexity
&lt;/h2&gt;

&lt;p&gt;Ok, so this wasn't too hard to visualize so far! In fact, you might have noticed that the NPATH values that we calculated were the same as the ones that we calculated with cyclomatic complexity. That's because, when functions are that small, both measurement methods are about the same. &lt;/p&gt;

&lt;p&gt;But let's make things a bit more complex now. Let's imagine that we have an interface that can convert an object to a string. We'll call it the &lt;code&gt;ToStringInterface&lt;/code&gt; interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function insert_default_value($mixed)
{
    if ($mixed instanceof ToStringInterface) {
        $mixed = $mixed-&amp;amp;gt;to_string();
    }

    if (!is_string($mixed) || empty($mixed)) {
        $mixed = 'value';
    }

    return $mixed;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once more, we updated our &lt;code&gt;insert_default_value&lt;/code&gt; function to use this interface. We start by checking if &lt;code&gt;mixed&lt;/code&gt; implements it using the &lt;a href="https://secure.php.net/manual/en/language.operators.type.php"&gt;&lt;code&gt;instanceof&lt;/code&gt;&lt;/a&gt; operator. If it does, we call the &lt;code&gt;to_string&lt;/code&gt; method and assign the value it returns to &lt;code&gt;mixed&lt;/code&gt;. The rest of the &lt;code&gt;insert_default_value&lt;/code&gt; function is the same.&lt;/p&gt;

&lt;p&gt;So what about now? Can you see how many unique paths there are through the &lt;code&gt;insert_default_value&lt;/code&gt; function? The answer is six. Yes, we doubled the number of paths through our code. (Yikes!)&lt;/p&gt;

&lt;h3&gt;
  
  
  Statements are multiplicative
&lt;/h3&gt;

&lt;p&gt;That's because, with NPATH, adding a new statement like this is multiplicative. That means that to get the total number of paths, we have to multiply the number of paths through the two &lt;code&gt;if&lt;/code&gt; conditions together. We already know how many paths there are through each &lt;code&gt;if&lt;/code&gt; condition because of our earlier examples.&lt;/p&gt;

&lt;p&gt;The first &lt;code&gt;if&lt;/code&gt; condition has two possible paths. It's whether &lt;code&gt;mixed&lt;/code&gt; implements the &lt;code&gt;ToStringInterface&lt;/code&gt; interface or not. And we saw before that the second &lt;code&gt;if&lt;/code&gt; condition has three possible paths. So the total number of paths is &lt;code&gt;2 * 3 = 6&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is also where NPATH and cyclomatic complexity diverge. This code only increased the cyclomatic complexity of our function by one. But having a cyclomatic complexity of four is still excellent. However, with NPATH, we can already see how much impact adding one more &lt;code&gt;if&lt;/code&gt; conditions can have.&lt;/p&gt;

&lt;h3&gt;
  
  
  Large functions or methods are dangerous
&lt;/h3&gt;

&lt;p&gt;The point of this example was to show that having a lot of conditionals in your function or method is dangerous. If we added a third conditional to our earlier example, you'd at least double your number of unique paths again. That means, that we'd have at least twelve unique paths through our code. &lt;/p&gt;

&lt;p&gt;But how often do we code we just three conditionals? Not that often! Most of the time, we can write functions or methods with a dozen or more conditionals in them. If you had a dozen conditionals in your code, it would have 4096 (2¹²) unique paths! (&lt;em&gt;gasp&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Now, a function or method with twelve unique paths is starting to get complicated. You can still visualize those twelve unique paths. It might just require that you stare at the code for a little while longer than usual.&lt;/p&gt;

&lt;p&gt;That said, with 4096 unique paths, that's impossible. (Well, that's unless you have some sort of superhuman ability! But, for us, mortals it's impossible.) Your code is now something beyond complicated. And it didn't take many statements to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many unique paths should your code have?
&lt;/h2&gt;

&lt;p&gt;This brings us to the obvious question, "How many unique paths is too many?" We know that 4096 is too many. But twelve is still quite reasonable if a bit on the complicated side.&lt;/p&gt;

&lt;p&gt;Code analysis tools tend to warn you at 200 unique paths. That's still quite a lot. Most of us can't visualize that many unique paths. &lt;/p&gt;

&lt;p&gt;But, again, that's subjective. It depends on the code or the person reading. That said, it's a safe bet to say that about 50 is a much more reasonable number of unique paths to have.&lt;/p&gt;

&lt;h1&gt;
  
  
  Managing complexity in our code
&lt;/h1&gt;

&lt;p&gt;So how do we get from a function or method that has 4096 unique paths to one that has around 50? The answer most of the time is to break your large function or method into smaller ones. For example, let's take our function or method with 4096 unique paths.&lt;/p&gt;

&lt;p&gt;Now, let's imagine that we broke that function or method in two. If we did that, it would have only six conditionals. (&lt;a href="https://en.wikipedia.org/wiki/Count_von_Count"&gt;Six! Ha! Ha! Ha!&lt;/a&gt;) How many unique paths would there be through that our function or method now? &lt;/p&gt;

&lt;p&gt;Well, we'd now only have 64 (2⁶) different unique paths in our function or method. That's a drastic reduction in complexity! And that's why breaking up a function or method is often the only thing that you need to do to reduce its complexity. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to break up functions or methods into smaller ones
&lt;/h2&gt;

&lt;p&gt;In practice, it's pretty rare that we can just split a function or method in two right down the middle. What will happen most of the time is that you'll only have small blocks of code that you can extract. So one function or method might become 3-4 functions or methods. The question then becomes what code is good to extract into a separate method or function. &lt;/p&gt;

&lt;h3&gt;
  
  
  Code that belongs together
&lt;/h3&gt;

&lt;p&gt;The easiest code to spot is code that logically belongs together. For example, let's imagine that you have a function or method where some of the code validates a date string. It could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;create_reminder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="nv"&gt;$date_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$formatted_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createFromFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date_format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$formatted_date&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$formatted_date&lt;/span&gt;&lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date_format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidArgumentException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;create_reminder&lt;/code&gt; function has an optional &lt;code&gt;date&lt;/code&gt; parameter. If we have a &lt;code&gt;date&lt;/code&gt;, we want to ensure that it follows the &lt;code&gt;Y-m-d H:i:s&lt;/code&gt; format. (You can find details on date formats &lt;a href="https://secure.php.net/manual/en/function.date.php#refsect1-function.date-parameters"&gt;here&lt;/a&gt;.) Otherwise, we throw an &lt;a href="https://secure.php.net/manual/en/class.invalidargumentexception.php"&gt;&lt;code&gt;InvalidArgumentException&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We do this by creating a &lt;a href="https://secure.php.net/manual/en/class.datetime.php"&gt;&lt;code&gt;DateTime&lt;/code&gt;&lt;/a&gt; object using the &lt;a href="https://secure.php.net/manual/en/datetime.createfromformat.php"&gt;&lt;code&gt;createFromFormat&lt;/code&gt;&lt;/a&gt; static method. It's a &lt;a href="https://carlalexander.ca/static-factory-method-pattern-wordpress/"&gt;static factory method&lt;/a&gt; that creates a &lt;code&gt;DateTime&lt;/code&gt; object by parsing a &lt;code&gt;time&lt;/code&gt; using a specific &lt;code&gt;format&lt;/code&gt; string. If it can't create a &lt;code&gt;DateTime&lt;/code&gt; object using the given &lt;code&gt;format&lt;/code&gt; string and &lt;code&gt;time&lt;/code&gt;, it returns &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The conditional first checks if &lt;code&gt;date&lt;/code&gt; is empty or not. Only if it's not empty do we use the &lt;code&gt;DateTime&lt;/code&gt; object that we created. We first check if it's &lt;code&gt;false&lt;/code&gt; and then we compare if our &lt;code&gt;formattedDate&lt;/code&gt; matches our &lt;code&gt;date&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We do that by using the &lt;a href="https://secure.php.net/manual/en/datetime.format.php"&gt;&lt;code&gt;format&lt;/code&gt;&lt;/a&gt; method. It converts our &lt;code&gt;DateTime&lt;/code&gt; object to a string matching the given &lt;code&gt;format&lt;/code&gt;. If the string returned by the &lt;code&gt;format&lt;/code&gt; method matches our &lt;code&gt;date&lt;/code&gt; string, we know it was correctly formatted.&lt;/p&gt;

&lt;h4&gt;
  
  
  Extracting the code
&lt;/h4&gt;

&lt;p&gt;While we can't see the rest of the &lt;code&gt;create_reminder&lt;/code&gt; function, it's not relevant here. We can see from what we have that this code is there to validate the &lt;code&gt;date&lt;/code&gt; argument. And this is what we want to extract into its function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;create_reminder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_reminder_date_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidArgumentException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;is_reminder_date_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$date_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$formatted_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createFromFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date_format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$date&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;$formatted_date&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$formatted_date&lt;/span&gt;&lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date_format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$date&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;As you can see above, we moved everything related to the validation of the &lt;code&gt;date&lt;/code&gt; to the &lt;code&gt;is_reminder_date_valid&lt;/code&gt; function. This function creates our&lt;code&gt;formattedDate&lt;/code&gt; &lt;code&gt;DateTime&lt;/code&gt; object using the &lt;code&gt;dateFormat&lt;/code&gt; variable. We then do the check to see if &lt;code&gt;formattedDate&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; and if the output from the &lt;code&gt;format&lt;/code&gt; method is identical to the given &lt;code&gt;date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In practice, this only removed one check from our conditional. This means that the cyclomatic complexity value of our &lt;code&gt;create_reminder&lt;/code&gt; function would also go down by one. You could also have moved the empty check into the &lt;code&gt;is_reminder_date_valid&lt;/code&gt; function and then it would have reduced it by two.&lt;/p&gt;

&lt;p&gt;But let's keep the code that we have already. Now, reducing the complexity of method by one might seem insignificant. That said, it can have quite an impact due to the multiplicative nature of NPATH.&lt;/p&gt;

&lt;p&gt;Let's imagine that our &lt;code&gt;create_reminder&lt;/code&gt; function had two other &lt;code&gt;if&lt;/code&gt; statements with a single condition in them. This would mean that our &lt;code&gt;create_reminder&lt;/code&gt; function had &lt;code&gt;2 * 2 * 4 = 16&lt;/code&gt; unique paths. (This is similar to our earlier example.) With our new &lt;code&gt;if&lt;/code&gt; statement using the &lt;code&gt;is_reminder_date_valid&lt;/code&gt; function, we'd have &lt;code&gt;2 * 2 * 3 = 12&lt;/code&gt; unique paths. &lt;/p&gt;

&lt;p&gt;That's a reduction of 25% in the total number of unique paths in your code. So it's not that insignificant in practice. That's why you should never think that extracting code for even one conditional statement is a waste of time. It's always worth it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Large conditional statements
&lt;/h3&gt;

&lt;p&gt;As we saw in the previous example, removing even one condition in an &lt;code&gt;if&lt;/code&gt; statement can have a significant impact. The natural progression of this is to move entire conditional blocks into their functions or methods. This makes a lot of sense if the entire condition block is just to validate one thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'headers'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'headers'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'headers'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nf"&gt;InvalidArgumentException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Here's an example using a fictional &lt;code&gt;send_response&lt;/code&gt; function. The function starts with a large &lt;code&gt;if&lt;/code&gt; statement containing three conditionals. They're there to ensure that the &lt;code&gt;response&lt;/code&gt; array contains a &lt;code&gt;status&lt;/code&gt; header inside the &lt;code&gt;headers&lt;/code&gt; subarray. &lt;/p&gt;

&lt;p&gt;This type of conditional pattern is widespread with multi-dimensional arrays like this one. But it's also something that you'll use a lot when you use &lt;a href="https://secure.php.net/manual/en/internals2.opcodes.instanceof.php"&gt;&lt;code&gt;instanceof&lt;/code&gt;&lt;/a&gt; to check the type of a variable. In all those cases, you have to validate the type and structure of the variable before interacting with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;response_has_status_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nf"&gt;InvalidArgumentException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;response_has_status_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'headers'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'headers'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'headers'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So to reduce the complexity of the &lt;code&gt;send_response&lt;/code&gt; function, we created the &lt;code&gt;response_has_status_header&lt;/code&gt; function. The &lt;code&gt;response_has_status_header&lt;/code&gt; function contains the &lt;a href="https://en.wikipedia.org/wiki/Inverse_(logic)"&gt;logical inverse&lt;/a&gt; of our previous condition. That's because we want the function to return &lt;code&gt;true&lt;/code&gt; if there's a status header. The previous condition returned &lt;code&gt;true&lt;/code&gt; if there wasn't one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Aren't we just hiding the problem?
&lt;/h3&gt;

&lt;p&gt;So this is a question you might have after seen how we break up large functions or methods into smaller ones. After all, the only thing that we've done is move code from one function or method to another. How can doing that reduce the complexity of your code?&lt;/p&gt;

&lt;p&gt;That's because what we've seen is how to evaluate complexity within the scope of a function or method. We're not trying to evaluate the complexity of the software as a whole. That said, there's a correlation between the two. (That's why a lot of tools only analyze function or method complexity.)&lt;/p&gt;

&lt;p&gt;So yes, simply moving code to a separate function or method can have a positive effect. You're not hiding the problem by doing that. But this only applies to code that's complex, not code that's complicated.&lt;/p&gt;

&lt;p&gt;If your code was complicated, moving some of it to another function or method won't make it less so. (Well, that's unless what made it complicated was the size of the function or method!) Instead, you'd have to focus on fixing the things that made your code hard to understand in the first place. (That's a topic for another article!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining conditionals together
&lt;/h2&gt;

&lt;p&gt;While breaking functions or methods into smaller ones does fix most issues with complexity, it's not the only solution either. There's one other way to reduce complexity that's worth talking about. That's combining conditionals together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function insert_default_value($mixed)
{
    if ($mixed instanceof ToStringInterface) {
        $mixed = $mixed-&amp;amp;gt;to_string();
    }

    if (!is_string($mixed) || empty($mixed)) {
        $mixed = 'value';
    }

    return $mixed;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's our &lt;code&gt;insert_default_value&lt;/code&gt; function that we were working with earlier. As we saw, this function had an NPATH value of six. Now, let's imagine that the &lt;code&gt;to_string&lt;/code&gt; method can never return an empty string.&lt;/p&gt;

&lt;p&gt;This means that we don't need to have two separate &lt;code&gt;if&lt;/code&gt; statements. Of course, we could keep them as is anyways. But what would happen if we changed our &lt;code&gt;insert_default_value&lt;/code&gt; function to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function insert_default_value($mixed)
{
    if ($mixed instanceof ToStringInterface) {
        $mixed = $mixed-&amp;amp;gt;to_string();
    } elseif (!is_string($mixed) || empty($mixed)) {
        $mixed = 'value';
    }

    return $mixed;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we combined our two &lt;code&gt;if&lt;/code&gt; statements using an &lt;code&gt;elseif&lt;/code&gt; statement, the NPATH value of the function goes from six to four. That's a 33% drop in the number of paths in our code. That's quite significant!&lt;/p&gt;

&lt;p&gt;This happened because we added one more path to our three paths from earlier. And then we removed the two paths &lt;code&gt;if&lt;/code&gt; statement that we had initially. So our NPATH calculation went from &lt;code&gt;2 * 3 = 6&lt;/code&gt; to just &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;While showing you how to calculate cyclomatic complexity and NPATH values is nice, it's not that practical. Most of us aren't going to go back through your code and do this for every function and method that we have already. You need tools to scan all your code and find the functions and methods with high complexity values for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command-line tools
&lt;/h3&gt;

&lt;p&gt;The first set of tools that we'll look at are command-line tools. These tools are a good starting point since they're free and you can use them on your development machine. PHP has two popular command-line tools that can analyze the complexity of your code: &lt;a href="https://github.com/squizlabs/PHP_CodeSniffer"&gt;PHP code sniffer&lt;/a&gt; and &lt;a href="https://phpmd.org/"&gt;PHP mess dectector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;PHP code sniffer is a tool for enforcing specific coding standards throughout out your code. Its main purpose isn't to manage the complexity of your code. That said, it does allow you to enforce that your functions or methods be below a specific cyclomatic complexity value. Unfortunately, it doesn't support NPATH as a complexity measuring method.&lt;/p&gt;

&lt;p&gt;Unlike PHP code sniffer, PHP mess detector is a tool that whose purpose is to help you detect problems with your code. It offers support for both cyclomatic complexity and NPATH measurement methods. It also has a lot of rules to help make your code less complicated on top of less complex.&lt;/p&gt;

&lt;p&gt;In practice, you should consider using both tools in your projects. But that might be a bit overwhelming if you haven't used either tool before. So, if you had to pick one, it would be PHP mess detector. It's the better choice for the task of evaluating the complexity of your code. &lt;/p&gt;

&lt;h3&gt;
  
  
  Code quality services
&lt;/h3&gt;

&lt;p&gt;If you work in a team, using a command line tool might not be enough for you. You might want a more automated way to check and enforce low complexity in everyone's code. In that scenario, you might want to use a code quality service instead of a command-line tool.&lt;/p&gt;

&lt;p&gt;Code quality services work by connecting to your git repository. Using that connection, they analyze your code each time that there's a commit or a new pull request. If there's an issue, they alert you via your chosen communication method. They also support status messages for GitHub and other git repository hosting services.&lt;/p&gt;

&lt;p&gt;In terms of choice, PHP has a bit more of a limited selection of code quality services. The big three to choose from are &lt;a href="https://www.codacy.com"&gt;Codacity&lt;/a&gt;,&lt;br&gt;
 &lt;a href="https://codeclimate.com"&gt;Code Climate&lt;/a&gt; and &lt;a href="https://scrutinizer-ci.com/"&gt;Scrutinizer&lt;/a&gt;. All three are pretty much the same in terms of features.&lt;/p&gt;

&lt;p&gt;The big difference between them is the price. They all offer free integrations for open source projects. But both Codacity and Code Climate charge per user per month which can make them quite pricey. Scrutinizer only charges a flat price per month.&lt;/p&gt;

&lt;h1&gt;
  
  
  Complexity isn't that complex
&lt;/h1&gt;

&lt;p&gt;So this took us a little while, but we've made it through! Complexity is a topic that can be quite intimidating to developers. But that's because of the theory and the language surrounding the concept. They make it seem more complicated than it is in practice.&lt;/p&gt;

&lt;p&gt;The truth is that managing software complexity is almost only about the size of your functions and methods. The mathematics behind it is just there as a way to quantify the effect of the size of your function or method. But it's not necessary for you to be able to do that to reduce complexity in your code.&lt;/p&gt;

&lt;p&gt;Just focus on keeping your functions and methods small. If you see that they're getting large, find a way to break them into smaller ones. That's all that there is to it.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwarecomplexity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>How to autowire a standalone Symfony console application</title>
      <dc:creator>Carl Alexander</dc:creator>
      <pubDate>Thu, 10 Dec 2020 07:34:42 +0000</pubDate>
      <link>https://forem.com/carlalexander/how-to-autowire-a-symfony-console-application-4618</link>
      <guid>https://forem.com/carlalexander/how-to-autowire-a-symfony-console-application-4618</guid>
      <description>&lt;p&gt;Working with the &lt;a href="https://symfony.com/doc/current/components/console.html"&gt;Symfony console component&lt;/a&gt; has always been a delightful experience. It makes it super easy to build a &lt;a href="https://en.wikipedia.org/wiki/Console_application"&gt;console application&lt;/a&gt;. You might need one while working with a framework. (Like &lt;a href="https://symfony.com/"&gt;Symfony&lt;/a&gt; or &lt;a href="https://laravel.com/"&gt;Laravel&lt;/a&gt;) Or you might just need to build a standalone console application.&lt;/p&gt;

&lt;p&gt;That said, there are differences between building a standalone console application and building one with a framework. The biggest one is the use of &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection"&gt;dependency injection&lt;/a&gt;. Most standalone Symfony console applications that you can find out there don't use it.&lt;/p&gt;

&lt;p&gt;That's a shame because the &lt;a href="https://symfony.com/doc/current/components/dependency_injection.html"&gt;Symfony dependency injection component&lt;/a&gt; is full of awesome features. For the &lt;a href="https://ymirapp.com"&gt;Ymir&lt;/a&gt; console application, I decided to look at how to build a console application using it. It's made building console applications even better.&lt;/p&gt;

&lt;h1&gt;
  
  
  A quick overview of the Symfony dependency injection component
&lt;/h1&gt;

&lt;p&gt;Let's take a brief look at how to use the Symfony dependency injection component before integrating it. Central to the component is the &lt;a href="https://symfony.com/doc/current/service_container.html"&gt;service container&lt;/a&gt;. To build the service container, you need to use the &lt;code&gt;ContainerBuilder&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Now, you can manually build the container by defining parameters and services yourself. That said, it's much more common to use the &lt;a href="https://symfony.com/doc/current/components/config.html"&gt;Symfony config component&lt;/a&gt; to do it. The component lets you use YAML (with the help of the &lt;a href="https://symfony.com/doc/current/components/yaml.html"&gt;YAML component&lt;/a&gt;, XML or plain PHP files to configure the container.&lt;/p&gt;

&lt;p&gt;This is what we're trying to do in this article. We want to configure a dependency injection container using a YAML file. (Feel free to use the file type that you prefer!) And then we want to compile the container and start the application using it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is all the code that you'd need to start the console application. No need to add any commands or deal with any dependencies. The configured container would have everything wired and ready to go for you.&lt;/p&gt;

&lt;h1&gt;
  
  
  Composer dependencies
&lt;/h1&gt;

&lt;p&gt;With this small intro out of the way, Let's start with Composer dependencies that we'll need for this console application. We've mentioned them all already.&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;"require"&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;"symfony/config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/console"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/dependency-injection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/yaml"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.4"&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;Above is the relevant section of the &lt;code&gt;composer.json&lt;/code&gt; file for these dependencies. We need four Symfony components. These are the config, console, dependency injection and YAML components. &lt;/p&gt;

&lt;p&gt;As mentioned earlier, the YAML component is optional. If you want to use PHP or XML to define your services, you don't need it. But for this article, we will use YAML for the services configuration so the dependency is necessary.&lt;/p&gt;

&lt;p&gt;Another thing is that we're going to use &lt;a href="https://symfony.com/releases/4.4"&gt;version 4.4&lt;/a&gt; of the Symfony components. This is current &lt;a href="https://en.wikipedia.org/wiki/Long-term_support"&gt;long-term support&lt;/a&gt; version of Symfony. So the code that you'll see will be good until the end of 2023. (And possibly longer.)&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the executable
&lt;/h1&gt;

&lt;p&gt;Once the dependencies added and downloaded, we can start working on the console application. The primary interaction point with it is an &lt;a href="https://en.wikipedia.org/wiki/Executable"&gt;executable file&lt;/a&gt;. Below is the base executable file that we're going to use for our console application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;#!/usr/bin/env php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our PHP script starts with &lt;code&gt;#!&lt;/code&gt; which is known as a &lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"&gt;hashbang&lt;/a&gt;. A hashbang tells a &lt;a href="https://en.wikipedia.org/wiki/Unix-like"&gt;Unix-like&lt;/a&gt; operating system that it should interpret a text file as an executable file. (You also need to make it executable using &lt;code&gt;chmod +x&lt;/code&gt;.) Following the hashbang is the interpreter that we want Unix to use.&lt;/p&gt;

&lt;p&gt;Here, we want that interpreter to be PHP. We could use &lt;code&gt;/usr/bin/php&lt;/code&gt;, but instead we used &lt;code&gt;/usr/bin/env php&lt;/code&gt;. The reason to use &lt;a href="https://en.wikipedia.org/wiki/Env"&gt;&lt;code&gt;env&lt;/code&gt;&lt;/a&gt; is that the script will not work if PHP isn't in the &lt;code&gt;/usr/bin&lt;/code&gt; directory. &lt;code&gt;env&lt;/code&gt; will search for the &lt;code&gt;php&lt;/code&gt; executable and then launch it. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;env&lt;/code&gt; is also useful if you have multiple PHP interpreters installed. This is especially common in local development. In that scenario, &lt;code&gt;env&lt;/code&gt; will always pick the default PHP interpreter that you've chosen.&lt;/p&gt;

&lt;p&gt;After the hashbang, the rest of our executable looks like a regular PHP script. We have the &lt;code&gt;&amp;lt;?php&lt;/code&gt; opening tag. We then &lt;a href="https://www.php.net/manual/en/function.require.php"&gt;require&lt;/a&gt; the autoload file generated by Composer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the container
&lt;/h2&gt;

&lt;p&gt;Now that we have our base executable, the next step is to add the dependency injection container. We want to configure and compile the container in the executable before running the console application. Let's look at what that looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;#!/usr/bin/env php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Config\FileLocator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\DependencyInjection\ContainerBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\DependencyInjection\Loader\YamlFileLoader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContainerBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;YamlFileLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileLocator&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nv"&gt;$loader&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/config/services.yml'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first start by instantiating a &lt;code&gt;ContainerBuilder&lt;/code&gt; object. We then use that &lt;code&gt;ContainerBuilder&lt;/code&gt; object to create a &lt;code&gt;YamlFileLoader&lt;/code&gt; object. This is the configuration loader that we're going to use to configure our dependency injection container.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;YamlFileLoader&lt;/code&gt; object will load the &lt;code&gt;services.yml&lt;/code&gt; file found in the &lt;code&gt;config&lt;/code&gt; directory. The directory and file names are a personal choice. You can name the YAML file whatever you want and put it wherever you want really. &lt;/p&gt;

&lt;p&gt;After loading it, we call the &lt;code&gt;compiler&lt;/code&gt; method. This compiles the &lt;code&gt;ContainerBuilder&lt;/code&gt; object and turns it into a &lt;code&gt;Container&lt;/code&gt; object. You cannot use the container until you've done this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the base dependency injection container configuration file
&lt;/h2&gt;

&lt;p&gt;Now, you could also load multiple configuration files if you wanted. But for this article, we're just going to use &lt;code&gt;services.yml&lt;/code&gt;. Here's what it contains so far.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;autowire&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_defaults&lt;/code&gt; section is where you define the default configuration options for the services in your container. The most important one is &lt;code&gt;autowire&lt;/code&gt; which you want to set to &lt;code&gt;true&lt;/code&gt;. This is what turns on the amazing &lt;a href="https://symfony.com/doc/current/service_container/autowiring.html"&gt;autowiring feature&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When &lt;code&gt;autowire&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;, the container will take over the handling of the dependencies for your services. It'll read the type hints of the constructor of each service. It'll then pass it the right dependencies when it creates the object the first time. (It's a really magical thing!)&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the Application class
&lt;/h1&gt;

&lt;p&gt;Now that we have our compiled &lt;code&gt;Container&lt;/code&gt; object, we can move on to our base &lt;code&gt;Application&lt;/code&gt; class. You can see it below extending the Symfony &lt;code&gt;Application&lt;/code&gt; class. We renamed it to &lt;code&gt;BaseApplication&lt;/code&gt; to prevent a naming conflict.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Console\Application&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;BaseApplication&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseApplication&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty like this, a console application doesn't do much. If you ran the &lt;code&gt;list&lt;/code&gt; command for it, you'd get this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XajQSDbq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://carlalexander.ca/uploads/2020/12/basic-list-command.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XajQSDbq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://carlalexander.ca/uploads/2020/12/basic-list-command.png" alt="" width="1698" height="1144" class="aligncenter size-full wp-image-2930"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are only two available commands: &lt;code&gt;help&lt;/code&gt; and &lt;code&gt;list&lt;/code&gt;. This is how all Symfony console applications start out with. So what we really need to do now is add commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding commands to the application
&lt;/h2&gt;

&lt;p&gt;Adding commands to an application is straightforward. All that you have to do is call the &lt;code&gt;add&lt;/code&gt; method with the Symfony &lt;code&gt;Command&lt;/code&gt; object you want to add. In our &lt;code&gt;Application&lt;/code&gt; class, this could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Console\Application&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;BaseApplication&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseApplication&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Constructor.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$commands&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;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;As you can see above, we added a constructor to the &lt;code&gt;Application&lt;/code&gt; class. The constructor has a single parameter called &lt;code&gt;commands&lt;/code&gt;. We use it to pass an array of &lt;code&gt;Command&lt;/code&gt; objects to the constructor. We then loop through each one and add them using the  &lt;code&gt;add&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Right now, we'd have to supply that array of &lt;code&gt;Command&lt;/code&gt; objects to the constructor ourselves. But what we really want is for the dependency injection container to handle all that for us. It should find all the &lt;code&gt;Command&lt;/code&gt; objects and pass them to the &lt;code&gt;Application&lt;/code&gt; constructor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a service tag for command objects
&lt;/h3&gt;

&lt;p&gt;To do that, we're going to use a &lt;a href="https://symfony.com/doc/current/service_container/tags.html"&gt;service tag&lt;/a&gt; to identify all our &lt;code&gt;Command&lt;/code&gt; objects. We can do that easily using the &lt;code&gt;_instanceof&lt;/code&gt; option. Here's what it looks like in the &lt;code&gt;services.yml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;autowire&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;_instanceof&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;Symfony\Component\Console\Command\Command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;command'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added the &lt;code&gt;_instanceof&lt;/code&gt; section below the &lt;code&gt;_default&lt;/code&gt; one. Under that section, you want to add the &lt;a href="https://www.php.net/manual/en/language.namespaces.rules.php"&gt;fully qualified name&lt;/a&gt; of the class or interface that we want to add a tag to. For this, we want to use the fully qualified of the Symfony &lt;code&gt;Command&lt;/code&gt; class because it doesn't have an interface. That fully qualified name is &lt;code&gt;Symfony\Component\Console\Command\Command&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Below that, we have the &lt;code&gt;tags&lt;/code&gt; that we want all the Symfony &lt;code&gt;Command&lt;/code&gt; objects to have. We just have one for this. It's &lt;code&gt;command&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the service tag
&lt;/h3&gt;

&lt;p&gt;Next, we need to configure the dependency injection container. Right now, it doesn't know that we want it to pass all the &lt;code&gt;Command&lt;/code&gt; objects to the &lt;code&gt;Application&lt;/code&gt; class constructor. To tell it to use our service tag, we have to modify the &lt;code&gt;services.yml&lt;/code&gt;  file like so:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;autowire&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;_instanceof&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;Symfony\Component\Console\Command\Command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;command'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="s"&gt;Console\Application&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;public&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!tagged&lt;/span&gt; &lt;span class="s"&gt;command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above you can see that we added the fully qualified name of the &lt;code&gt;Application&lt;/code&gt; class as a new section. Below, we added the &lt;code&gt;arguments&lt;/code&gt; section which lets you define the arguments to pass to a class constructor. We then used &lt;code&gt;!tagged command&lt;/code&gt; as the first argument.&lt;/p&gt;

&lt;p&gt;At this point, if you try to run your console application, you'll get an error 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;PHP Fatal error:  Uncaught TypeError: Argument 1 passed to Console\Application::__construct() must be of the type array, object given
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's because the dependency injection container doesn't pass an array to our &lt;code&gt;Application&lt;/code&gt; class constructor. It passes an object implementing the &lt;a href="https://www.php.net/manual/en/class.traversable.php"&gt;&lt;code&gt;Traversable&lt;/code&gt;&lt;/a&gt; interface. This is a built-in PHP interface that lets an object work with a &lt;a href="https://www.php.net/manual/en/control-structures.foreach.php"&gt;&lt;code&gt;foreach&lt;/code&gt;&lt;/a&gt; loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Console\Application&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;BaseApplication&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseApplication&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Constructor.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;iterable&lt;/span&gt; &lt;span class="nv"&gt;$commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$commands&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;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;To solve this issue, we need to change the type hint of the &lt;code&gt;Application&lt;/code&gt; class constructor. If you want, you can use the &lt;code&gt;Traversable&lt;/code&gt; interface. A better option is the &lt;a href="https://www.php.net/manual/en/language.types.iterable.php"&gt;&lt;code&gt;iterable&lt;/code&gt;&lt;/a&gt; pseudo-type which is what the code above uses. But regardless of the one you use, once you update the constructor, the error will be gone.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding the application to the executable
&lt;/h1&gt;

&lt;p&gt;At this point, you have everything that you need to run a console application using the dependency injection container. We just need to update the executable so we get the &lt;code&gt;Application&lt;/code&gt; object from the container. You can see that below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;#!/usr/bin/env php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Console\Application&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Config\FileLocator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\DependencyInjection\ContainerBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\DependencyInjection\Loader\YamlFileLoader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContainerBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;YamlFileLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileLocator&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nv"&gt;$loader&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/config/services.yml'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top, we added the &lt;code&gt;use&lt;/code&gt; statement to import the &lt;code&gt;Application&lt;/code&gt; class. But really, what we care about at the bottom. It's the call to the &lt;a href="https://www.php.net/manual/en/function.exit.php"&gt;&lt;code&gt;exit&lt;/code&gt;&lt;/a&gt; function. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;exit&lt;/code&gt; function just terminated the PHP script using the given &lt;code&gt;status&lt;/code&gt;. The &lt;code&gt;status&lt;/code&gt; just comes from getting the &lt;code&gt;Application&lt;/code&gt; object from the container. And then we call the &lt;code&gt;run&lt;/code&gt; method which runs the console application and terminates.&lt;/p&gt;

&lt;p&gt;To see a fully working version of a console application using this technique, I invite you to look at the code of the &lt;a href="https://github.com/ymirapp/cli"&gt;Ymir console application on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://carlalexander.ca/symfony-console-application-autowire/"&gt;Source&lt;/a&gt;&lt;/p&gt;

</description>
      <category>commandlineinterface</category>
      <category>dependencyinjection</category>
      <category>symfony</category>
      <category>symfonycomponents</category>
    </item>
  </channel>
</rss>
