<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abderrahim El Ouariachi</title>
    <description>The latest articles on DEV Community by Abderrahim El Ouariachi (@itsabderrahimel).</description>
    <link>https://dev.to/itsabderrahimel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4137490%2Ffa626cf5-4267-4302-99e3-af124e13d5cd.jpg</url>
      <title>DEV Community: Abderrahim El Ouariachi</title>
      <link>https://dev.to/itsabderrahimel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/itsabderrahimel"/>
    <language>en</language>
    <item>
      <title>Keep Tenancy Out of Your Laravel App: Structuring stancl/tenancy v4 as a Module</title>
      <dc:creator>Abderrahim El Ouariachi</dc:creator>
      <pubDate>Wed, 21 Oct 2026 21:22:35 +0000</pubDate>
      <link>https://dev.to/itsabderrahimel/keep-tenancy-out-of-your-laravel-app-structuring-stancltenancy-v4-as-a-module-3iel</link>
      <guid>https://dev.to/itsabderrahimel/keep-tenancy-out-of-your-laravel-app-structuring-stancltenancy-v4-as-a-module-3iel</guid>
      <description>&lt;p&gt;When you develop a multi-tenant application, it's easy for the code of your central application to get entangled with the tenant code. Or you start splitting everything into Central and Tenant folders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Models
  |-&amp;gt; Central
  |-&amp;gt; Tenant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but there is a better way: modules.&lt;/p&gt;

&lt;p&gt;In this post, we will talk about how to do that.&lt;/p&gt;

&lt;p&gt;The stack I'm using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel 13&lt;/li&gt;
&lt;li&gt;stancl/tenancy v4&lt;/li&gt;
&lt;li&gt;Pest 4&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How a module is usually structured
&lt;/h2&gt;

&lt;p&gt;First, we need to decide on a structure. Most developers structure their modules 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;Modules/Billing/
├── src/
│   ├── Domain/
│   ├── Application/
│   └── Http/
├── config/
├── database/
├── routes/
└── tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I decided to structure it differently, using the structure you are already used to working with: the default Laravel structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;modules/
└── Tenancy/
    ├── app/
    │   ├── Http/
    │   │   └── Controllers/
    │   │       └── Controller.php
    │   ├── Models/
    │   │   ├── SomeModel.php
    │   │   └── AnotherModel.php
    │   └── Providers/
    │       ├── TenancyModuleServiceProvider.php
    │       └── TenancyServiceProvider.php
    ├── config/
    │   └── tenancy.php
    ├── database/
    │   ├── factories/
    │   ├── migrations/
    │   └── seeders/
    └── routes/
        └── tenant.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this structure, you can easily reason about where to put your code. And if you use PHPStorm, you can use its scopes feature to list only the module code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxyt6zjrx6tm1m1k7pude.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxyt6zjrx6tm1m1k7pude.png" alt="PHPStorm scopes panel showing only the Tenancy module files" width="298" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The feature is located in Settings -&amp;gt; Appearance &amp;amp; Behavior -&amp;gt; Scopes.&lt;/p&gt;

&lt;p&gt;This way, the code of the central application won't bother you.&lt;/p&gt;

&lt;p&gt;I decided to leave the frontend code and the tests in there normal folders &lt;code&gt;resources&lt;/code&gt; and &lt;code&gt;tests&lt;/code&gt;. The tenancy code will never be separated from the central application, because one cannot live without the other, so I like to keep my frontend and my tests in a single place. You can still include those folders in the scope, so only the tests and resources related to Tenancy are shown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now that the structure is ready, we need to register the module.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  How to register the Tenancy module
&lt;/h2&gt;

&lt;p&gt;The stancl/tenancy v4 package comes with a &lt;code&gt;TenancyServiceProvider&lt;/code&gt; and a config file named &lt;code&gt;tenancy.php&lt;/code&gt;. We will move them to &lt;code&gt;modules/Tenancy/app/Providers&lt;/code&gt; and &lt;code&gt;modules/Tenancy/config&lt;/code&gt; respectively. We also need to create our own service provider to register the routes, config files, and providers. It will 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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Modules\Tenancy\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="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenancyModuleServiceProvider&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="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$moduleRoot&lt;/span&gt; &lt;span class="o"&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;'/../../'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @var array&amp;lt;int, class-string&amp;lt;TenancyServiceProvider&amp;gt;&amp;gt;
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;TenancyServiceProvider&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="c1"&gt;// Other service providers...&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;registerConfigFiles&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;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$provider&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;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$provider&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;loadRoutesFrom&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;moduleRoot&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'routes/tenant.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;registerConfigFiles&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;glob&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;moduleRoot&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'config/*.php'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="c1"&gt;// use set to register the config files&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;$files&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.php'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="nv"&gt;$file&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;Then register the module's provider in &lt;code&gt;bootstrap/providers.php&lt;/code&gt;, and remove the old &lt;code&gt;App\Providers\TenancyServiceProvider&lt;/code&gt; and &lt;code&gt;config/tenancy.php&lt;/code&gt; from the central app:&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;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;App\Providers\AppServiceProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;Modules\Tenancy\App\Providers\TenancyModuleServiceProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After moving the config file, set the migration paths in it to the following:&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="s1"&gt;'migration_parameters'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'--force'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This needs to be true to run migrations in production.&lt;/span&gt;
    &lt;span class="s1"&gt;'--path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'modules/Tenancy/database/migrations'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="s1"&gt;'--schema-path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'modules/Tenancy/database/schema/tenant-schema.dump'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'--realpath'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also make sure you set &lt;code&gt;CACHE_STORE&lt;/code&gt; in &lt;code&gt;phpunit.xml&lt;/code&gt; to &lt;code&gt;file&lt;/code&gt; if you're using the &lt;code&gt;RedisTenancyBootstrapper&lt;/code&gt;, because it does not work otherwise.&lt;/p&gt;

&lt;p&gt;Now we can move on to Composer. After making these updates, you need to register the namespaces in &lt;code&gt;composer.json&lt;/code&gt; and run &lt;code&gt;composer dump-autoload&lt;/code&gt;:&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="nl"&gt;"autoload"&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;"psr-4"&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;"App\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Database\\Factories\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database/factories/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Database\\Seeders\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database/seeders/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Modules\\Tenancy\\App\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/Tenancy/app/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Modules\\Tenancy\\Database\\Factories\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/Tenancy/database/factories/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Modules\\Tenancy\\Database\\Seeders\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/Tenancy/database/seeders/"&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="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Structuring the tests
&lt;/h2&gt;

&lt;p&gt;As I said before, I like to keep the tests in the root &lt;code&gt;tests&lt;/code&gt; folder. An easy way to structure them is to split them 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;tests
├── Central
│   ├── Browser
│   ├── Feature
│   └── Unit
└── Tenant
    ├── Browser
    ├── Feature
    └── Unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using PHPStorm's scopes feature, you can include the &lt;code&gt;Tenant&lt;/code&gt; folder, so when we select the scope, the &lt;code&gt;modules/Tenancy&lt;/code&gt; folder and its tests will be the only ones shown.&lt;/p&gt;

&lt;p&gt;If you decide to do this, update the test suites in the &lt;code&gt;phpunit.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;testsuites&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;testsuite&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Central"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;tests/Central&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/testsuite&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;testsuite&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Tenant"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;tests/Tenant&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/testsuite&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/testsuites&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;source&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;include&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;app&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;modules/Tenancy/app&lt;span class="nt"&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/include&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/source&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Making your tools aware of the module
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Laravel Idea
&lt;/h3&gt;

&lt;p&gt;If you use the Laravel Idea plugin, you can make it module-aware. In my case, I'm using PHPStorm.&lt;/p&gt;

&lt;p&gt;First, configure how you are using modules. You can find it in &lt;code&gt;Languages &amp;amp; Frameworks -&amp;gt; Laravel Idea -&amp;gt; Module System&lt;/code&gt;. Configure it like below:&lt;/p&gt;

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

&lt;p&gt;Next, go to &lt;code&gt;Languages &amp;amp; Frameworks -&amp;gt; Laravel Idea -&amp;gt; Code Generation -&amp;gt; Standard Generation&lt;/code&gt;. Set the "Available in modules" flag to true for all the code generation types, and include the path where you want the code to be generated.&lt;/p&gt;

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

&lt;p&gt;Now, just use the shortcut and you will be able to generate code really fast for the Tenancy module. When generating some code, you will be prompted to choose between generating it for the central application or for a specific module.&lt;/p&gt;

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

&lt;p&gt;You can also change the templates used for the generated code, which gives you a lot of freedom when it comes to customization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Octane
&lt;/h3&gt;

&lt;p&gt;If you're using Octane, you need to set the &lt;code&gt;--watch&lt;/code&gt; flag in the server command, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/bin/php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;variables_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;EGPCS /var/www/html/artisan octane:start &lt;span class="nt"&gt;--watch&lt;/span&gt; &lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;swoole &lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'${APP_PORT:-80}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and add the paths of the module files to the Octane config, like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'watch'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'bootstrap'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'config/**/*.php'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'database/**/*.php'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'public/**/*.php'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'resources/**/*.php'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'routes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'modules/Tenancy/routes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'modules/Tenancy/app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'modules/Tenancy/database/**/*.php'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'composer.lock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'.env'&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 way, the workers get reloaded when you change the routes or the tenant code.&lt;/p&gt;

&lt;h3&gt;
  
  
  PHPStan
&lt;/h3&gt;

&lt;p&gt;If you're using PHPStan, you also need to include the Tenancy code. You can do that in the &lt;code&gt;phpstan.neon&lt;/code&gt; file in the root of your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parameters:
    paths:
        - app/
        - bootstrap/app.php
        - config/
        - database/
        - routes/
        - tests/
        - modules/Tenancy/app
        - modules/Tenancy/database
        - modules/Tenancy/routes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rector
&lt;/h3&gt;

&lt;p&gt;Like the other tools, Rector needs to know about the module files. You can add the paths to the &lt;code&gt;withPaths&lt;/code&gt; method in the &lt;code&gt;rector.php&lt;/code&gt; file in the root of your project, like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;RectorConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&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;withPaths&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;'/app'&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;'/routes'&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;'/database'&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'&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;'/tests'&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;'/modules/Tenancy/app'&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;'/modules/Tenancy/routes'&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;'/modules/Tenancy/database'&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;withComposerBased&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;laravel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;withImportNames&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;withPhpSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;php85&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;withPreparedSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deadCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;codeQuality&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;codingStyle&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;typeDeclarations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;typeDeclarationDocblocks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withRules&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nc"&gt;SortTraitUsesByLengthRector&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;SortAttributesByLengthRector&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;I think that's all. If I missed something, please let me know in the comments.&lt;/p&gt;

&lt;p&gt;Structuring the tenancy code this way makes development easier and faster, because you avoid splitting everything into Central and Tenant folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Models
├── Central
├── Tenant
Http
├── Central
├── Tenant
Providers
├── Central
├── Tenant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this post was useful, please like it, and leave a comment.&lt;/p&gt;

&lt;p&gt;Thanks,&lt;br&gt;
&lt;a href="http://www.itsabderrahimel.com" rel="noopener noreferrer"&gt;www.itsabderrahimel.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>How to Set Up an SSL VPN on a FortiGate 40F (Step-by-Step)</title>
      <dc:creator>Abderrahim El Ouariachi</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:37:29 +0000</pubDate>
      <link>https://dev.to/itsabderrahimel/how-to-set-up-an-ssl-vpn-on-a-fortigate-40f-step-by-step-50kd</link>
      <guid>https://dev.to/itsabderrahimel/how-to-set-up-an-ssl-vpn-on-a-fortigate-40f-step-by-step-50kd</guid>
      <description>&lt;p&gt;&lt;strong&gt;Want to reach your local network from anywhere in the world?&lt;/strong&gt; In this guide, I'll show you how, using a &lt;strong&gt;FortiGate 40F&lt;/strong&gt; and &lt;strong&gt;SSL VPN&lt;/strong&gt;, configured entirely from the &lt;strong&gt;command line&lt;/strong&gt;. Every step is a block you can copy, review and keep in version control.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; SSL VPN was removed from FortiOS 7.6.3 and later on lower-end models (including the 40F), which are pushed toward IPsec instead. The commands in this guide apply to &lt;strong&gt;FortiOS 7.2 and 7.4&lt;/strong&gt;. Syntax can differ on other branches, so check with &lt;code&gt;?&lt;/code&gt; and tab completion on your own build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on values:&lt;/strong&gt; every address, name and secret below is an example. Replace anything in &lt;code&gt;&amp;lt;ANGLE_BRACKETS&amp;gt;&lt;/code&gt; and change the example subnets to match your network. Never publish or reuse real credentials, public IPs or certificate fingerprints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is an SSL VPN?
&lt;/h2&gt;

&lt;p&gt;An SSL VPN lets you connect to a private network that isn't directly reachable from the internet. Traffic is encrypted with TLS (the successor to SSL) and carried over HTTPS, so it usually passes through firewalls that block other VPN protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the CLI instead of the web UI?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repeatable:&lt;/strong&gt; paste the same blocks on a second firewall or after a factory reset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewable:&lt;/strong&gt; you can diff and comment a text file. You can't diff a series of screenshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precise:&lt;/strong&gt; the CLI shows every option, including the ones the GUI hides.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A FortiGate 40F with admin access&lt;/li&gt;
&lt;li&gt;A Linux machine, or any device with FortiClient, to test the connection&lt;/li&gt;
&lt;li&gt;A free TCP port for the VPN (we'll use &lt;code&gt;10443&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;(If behind a router) access to the router to configure port forwarding&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example values used in this guide
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Example value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WAN interface&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAN interface&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAN subnet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.20.0.0/24&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPN client pool&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;10.10.10.2&lt;/code&gt; – &lt;code&gt;10.10.10.254&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPN listen port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10443&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SSL-VPN-GROUP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SSL-VPN-PORTAL&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Example user&lt;/td&gt;
&lt;td&gt;&lt;code&gt;alice&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 0: Open the console and back up
&lt;/h2&gt;

&lt;p&gt;You can reach the CLI in three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;CLI Console&lt;/strong&gt; (&lt;code&gt;&amp;gt;_&lt;/code&gt; icon) in the top bar of the web UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH&lt;/strong&gt; to the FortiGate's management address&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;serial console&lt;/strong&gt; cable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back up the current configuration before changing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cisco_ios"&gt;&lt;code&gt;&lt;span class="k"&gt;execute&lt;/span&gt; backup config flash "before-ssl-vpn"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some basics for the rest of the guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each &lt;code&gt;config ... end&lt;/code&gt; block opens and closes a configuration section.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Tab&lt;/strong&gt; to complete a command and &lt;strong&gt;?&lt;/strong&gt; to list options.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;show&lt;/code&gt; prints the changes you made in a section, and &lt;code&gt;show full-configuration&lt;/code&gt; includes defaults.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create the user and group
&lt;/h2&gt;

&lt;p&gt;Create one account &lt;strong&gt;per person&lt;/strong&gt;. Shared accounts make it impossible to tell who did what, or to revoke one person's access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"alice"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;passwd&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;STRONG_PASSWORD&amp;gt;"&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-GROUP"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="s2"&gt;"alice"&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add a user to an existing group later without removing the other members, use &lt;code&gt;append&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-GROUP"&lt;/span&gt;
        &lt;span class="n"&gt;append&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="s2"&gt;"bob"&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two-factor authentication.&lt;/strong&gt; This guide leaves it off to keep the demo short. In production, turn it on. For example, with a FortiToken (mobile or hardware):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight augeas"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s"&gt;"alice"&lt;/span&gt;
        &lt;span class="kr"&gt;set&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="n"&gt;fortitoken&lt;/span&gt;
        &lt;span class="kr"&gt;set&lt;/span&gt; &lt;span class="n"&gt;fortitoken&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;TOKEN_SERIAL&amp;gt;"&lt;/span&gt;
        &lt;span class="kr"&gt;set&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;USER_EMAIL&amp;gt;"&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your build's options with &lt;code&gt;set two-factor ?&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Create the tunnel IP pool
&lt;/h2&gt;

&lt;p&gt;Clients that connect receive an address from this range. Pick a subnet that doesn't overlap any network the FortiGate already knows about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;firewall&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-RANGE"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;iprange&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;-&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;-&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;254&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Create the SSL VPN portal
&lt;/h2&gt;

&lt;p&gt;The portal defines what a connected user gets. Here we want &lt;strong&gt;tunnel mode&lt;/strong&gt; with &lt;strong&gt;no split tunneling&lt;/strong&gt;, so all client traffic goes through the firewall.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;vpn&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;portal&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-PORTAL"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;tunnel&lt;/span&gt;-&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;-&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;disable&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;-&lt;span class="n"&gt;pools&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-RANGE"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;-&lt;span class="n"&gt;tunneling&lt;/span&gt; &lt;span class="n"&gt;disable&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;-&lt;span class="n"&gt;user&lt;/span&gt;-&lt;span class="n"&gt;logins&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tunnel-mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gives the client a full VPN tunnel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;web-mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes the browser portal and its extra attack surface. Enable it only if you use bookmarks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;split-tunneling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All client traffic is sent through the tunnel and your firewall policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;limit-user-logins&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One session per user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ip-pools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SSL-VPN-RANGE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where clients get their address&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The portal also leaves "save password", "auto-connect" and "keep-alive" off by default. Leave them that way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Configure the SSL VPN settings
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config vpn ssl settings
    set status enable
    set port 10443
    set source-interface "wan"
    set source-address "all"
    set servercert "Fortinet_Factory"
    set https-redirect enable
    set tunnel-ip-pools "SSL-VPN-RANGE"
    set idle-timeout 300
    config authentication-rule
        edit 1
            set groups "SSL-VPN-GROUP"
            set portal "SSL-VPN-PORTAL"
        next
    end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;port&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10443&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Avoid 443, which can conflict with the management interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source-interface&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The interface that listens for VPN connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source-address&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fine for a lab. Restrict it in production (see below).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;servercert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Fortinet_Factory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The built-in default. Clients will see a certificate warning. Replace it in production.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;https-redirect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Redirects plain HTTP to the SSL VPN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idle-timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;300&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logs out idle users after 300 seconds. &lt;code&gt;0&lt;/code&gt; means never.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;authentication-rule&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;group → portal&lt;/td&gt;
&lt;td&gt;Only &lt;code&gt;SSL-VPN-GROUP&lt;/code&gt; members get &lt;code&gt;SSL-VPN-PORTAL&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Check the fallback mappings
&lt;/h3&gt;

&lt;p&gt;FortiOS ships with default portal mappings, and users who don't match your rule may still be mapped to a default portal. List them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show vpn ssl settings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the &lt;code&gt;authentication-rule&lt;/code&gt; entries and the &lt;code&gt;default-portal&lt;/code&gt; value. Remove any mapping you don't need, or point unmatched users at a portal that gives no access if your build has one (&lt;code&gt;show vpn ssl web portal&lt;/code&gt; lists what exists).&lt;/p&gt;

&lt;h3&gt;
  
  
  Restrict who can reach the VPN
&lt;/h3&gt;

&lt;p&gt;Allowing any host to reach a login page on the internet invites scanning and brute-force attempts. Limit the source, either to known addresses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;firewall&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"VPN-ALLOWED-SOURCES"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;iprange&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;-&lt;span class="n"&gt;ip&lt;/span&gt; &amp;lt;&lt;span class="n"&gt;ALLOWED_START_IP&lt;/span&gt;&amp;gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;-&lt;span class="n"&gt;ip&lt;/span&gt; &amp;lt;&lt;span class="n"&gt;ALLOWED_END_IP&lt;/span&gt;&amp;gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;vpn&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;-&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="s2"&gt;"VPN-ALLOWED-SOURCES"&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or to specific countries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;firewall&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"VPN-ALLOWED-COUNTRIES"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;geography&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;TWO_LETTER_COUNTRY_CODE&amp;gt;"&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;vpn&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;-&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="s2"&gt;"VPN-ALLOWED-COUNTRIES"&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Create the firewall policies
&lt;/h2&gt;

&lt;p&gt;You need two policies, both coming in from the tunnel interface &lt;code&gt;ssl.root&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one for traffic to the &lt;strong&gt;local network&lt;/strong&gt; behind the firewall&lt;/li&gt;
&lt;li&gt;one for traffic going out through the &lt;strong&gt;WAN&lt;/strong&gt; (internet access for clients, because split tunneling is off)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, create an address object for the LAN you want to expose, so the first policy doesn't have to use &lt;code&gt;all&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;firewall&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="s2"&gt;"LAN-SUBNET"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ipmask&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;subnet&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;20&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;.&lt;span class="m"&gt;255&lt;/span&gt;.&lt;span class="m"&gt;255&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the policies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;firewall&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-LAN-ACCESS"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;srcintf&lt;/span&gt; &lt;span class="s2"&gt;"ssl.root"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;dstintf&lt;/span&gt; &lt;span class="s2"&gt;"lan"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;srcaddr&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;dstaddr&lt;/span&gt; &lt;span class="s2"&gt;"LAN-SUBNET"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-GROUP"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt; &lt;span class="s2"&gt;"always"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="s2"&gt;"ALL"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;nat&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;logtraffic&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
    &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-WAN-ACCESS"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;srcintf&lt;/span&gt; &lt;span class="s2"&gt;"ssl.root"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;dstintf&lt;/span&gt; &lt;span class="s2"&gt;"wan"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;srcaddr&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;dstaddr&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-GROUP"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt; &lt;span class="s2"&gt;"always"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="s2"&gt;"ALL"&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;nat&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;logtraffic&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;srcaddr "all"&lt;/code&gt; plus a group?&lt;/strong&gt; In a user-based policy the group does the real filtering. &lt;code&gt;all&lt;/code&gt; just means "from any address in the tunnel".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About NAT on the LAN policy:&lt;/strong&gt; with &lt;code&gt;nat enable&lt;/code&gt;, LAN hosts see the firewall's own address instead of the VPN client's. That keeps routing simple. If you disable NAT, your LAN hosts need a return route to the VPN pool.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Don't leave &lt;code&gt;service "ALL"&lt;/code&gt; in production
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ALL&lt;/code&gt; services to a whole subnet is convenient for a lab and dangerous if a password leaks. Allow only what each user needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config firewall policy
    edit &amp;lt;POLICY_ID&amp;gt;
        set service "HTTPS" "SSH"
        set dstaddr "&amp;lt;SPECIFIC_SERVER&amp;gt;"
    next
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create one policy per role (admins, developers, contractors), each with its own group and destinations. This is the principle of least privilege. It matters even more here because &lt;strong&gt;the demo has two-factor authentication off&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the routing
&lt;/h3&gt;

&lt;p&gt;Tunnel clients need the FortiGate to know how to reach the internet and the LAN. Confirm there is a default route out of &lt;code&gt;wan&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cisco_ios"&gt;&lt;code&gt;&lt;span class="k"&gt;get&lt;/span&gt; router info routing-table all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Verify the configuration
&lt;/h2&gt;

&lt;p&gt;Before testing from a client, review what you entered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show vpn ssl settings
show vpn ssl web portal SSL-VPN-PORTAL
show firewall policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm that the SSL VPN is listening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;diagnose sys tcpsock | &lt;span class="nb"&gt;grep &lt;/span&gt;10443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7: Connect with a client
&lt;/h2&gt;

&lt;h3&gt;
  
  
  FortiClient (Windows, macOS, iOS, Android)
&lt;/h3&gt;

&lt;p&gt;Create an &lt;strong&gt;SSL-VPN&lt;/strong&gt; connection with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Remote gateway&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;ROUTER_PUBLIC_IP_OR_DDNS_NAME&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customize port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10443&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Username&lt;/td&gt;
&lt;td&gt;your own account&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You'll be prompted for the password each time, because the portal doesn't allow saving it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux with &lt;code&gt;openfortivpn&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;openfortivpn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time, you need to trust the firewall's certificate. Run the command with a wrong or missing &lt;code&gt;--trusted-cert&lt;/code&gt; and it will print the certificate's hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openfortivpn &amp;lt;VPN_ADDRESS&amp;gt;:10443 &lt;span class="nt"&gt;-u&lt;/span&gt; alice 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep &lt;/span&gt;trusted-cert

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# ERROR:      --trusted-cert &amp;lt;SHA256_FINGERPRINT&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify that this hash matches your firewall's certificate before trusting it&lt;/strong&gt; (for example, by viewing the certificate on the FortiGate). Then connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;openfortivpn &amp;lt;VPN_ADDRESS&amp;gt;:10443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; alice &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--trusted-cert&lt;/span&gt; &amp;lt;SHA256_FINGERPRINT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted for the password. Avoid &lt;code&gt;-p 'PASSWORD'&lt;/code&gt; on the command line, since it ends up in your shell history and is visible in the process list. If you use &lt;code&gt;/etc/openfortivpn/config&lt;/code&gt; to store settings, keep the password out of it and restrict the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/openfortivpn/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check the session on the firewall
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get vpn ssl monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the user, the source address and an address from your pool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Forward the port (if the firewall is behind a router)
&lt;/h2&gt;

&lt;p&gt;If your FortiGate sits behind a router (for example, a home or ISP router) instead of having a public IP directly on its WAN interface, the router has to pass the VPN traffic through. Create a &lt;strong&gt;port forwarding&lt;/strong&gt; rule on the router:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10443&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal IP&lt;/td&gt;
&lt;td&gt;The FortiGate's WAN interface IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10443&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then connect using the &lt;strong&gt;router's public IP&lt;/strong&gt; (or a domain name pointing to it) instead of the firewall's internal IP.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; port forwarding won't work if your ISP uses CGNAT (you don't have a real public IP). If your public IP changes often, use a dynamic DNS name. FortiGate can update a FortiGuard DDNS name itself, so you don't have to publish an IP address anywhere.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Troubleshooting from the CLI
&lt;/h2&gt;

&lt;p&gt;If the client can't connect, watch what the firewall sees. The first command shows whether traffic arrives at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;diagnose sniffer packet wan 'tcp port 10443' 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For authentication and tunnel problems, enable the SSL VPN debug, reproduce the problem, then switch it off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;diagnose debug application sslvpn -1
diagnose debug enable
# ...try to connect...
diagnose debug disable
diagnose debug reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Timeout or "server unreachable"&lt;/td&gt;
&lt;td&gt;Port forward missing, CGNAT, wrong port, stale DDNS&lt;/td&gt;
&lt;td&gt;The sniffer shows no packets: check the router and DNS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificate warning&lt;/td&gt;
&lt;td&gt;Default certificate in use&lt;/td&gt;
&lt;td&gt;Expected until you install a trusted certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login rejected&lt;/td&gt;
&lt;td&gt;Wrong password, user disabled, user not in the group&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;show user local&lt;/code&gt;, &lt;code&gt;show user group&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connects but reaches nothing&lt;/td&gt;
&lt;td&gt;Policy or route missing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;show firewall policy&lt;/code&gt;, &lt;code&gt;get router info routing-table all&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redirect warning&lt;/td&gt;
&lt;td&gt;Admin HTTP port conflicts with the redirect&lt;/td&gt;
&lt;td&gt;Keep the VPN on &lt;code&gt;10443&lt;/code&gt; and review the admin HTTP port&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Rollback
&lt;/h2&gt;

&lt;p&gt;If something goes wrong, undo the changes in this order. Take a backup first if you made changes since Step 0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;firewall&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;
    &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-LAN-ACCESS"&lt;/span&gt;
    &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="s2"&gt;"SSL-VPN-WAN-ACCESS"&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;vpn&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;disable&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or restore the backup you took at the start from the &lt;strong&gt;web UI&lt;/strong&gt; (System → Configuration → Revisions).&lt;/p&gt;




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

&lt;p&gt;You now have a working SSL VPN on your FortiGate 40F, configured and verified from the console. Before using it in production, I recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;enabling two-factor authentication&lt;/strong&gt; for every user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;restricting access&lt;/strong&gt; by source address or country, and by destination subnet and service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;using a certificate from a trusted CA&lt;/strong&gt; instead of the factory default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;adding security profiles&lt;/strong&gt; (antivirus, IPS, web filter) to the policies, since the demo uses none&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reviewing the default portal mappings&lt;/strong&gt; so unmatched users get nothing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;planning your move to IPsec&lt;/strong&gt;, because SSL VPN is gone from the 40F on FortiOS 7.6.3 and later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions or suggestions? Leave a comment below!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>networking</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>Making stancl/tenancy tests 3x faster in Laravel</title>
      <dc:creator>Abderrahim El Ouariachi</dc:creator>
      <pubDate>Thu, 01 Oct 2026 14:15:29 +0000</pubDate>
      <link>https://dev.to/itsabderrahimel/making-stancltenancy-tests-3x-faster-in-laravel-2hm8</link>
      <guid>https://dev.to/itsabderrahimel/making-stancltenancy-tests-3x-faster-in-laravel-2hm8</guid>
      <description>&lt;p&gt;If you are using tenancy with a Laravel application, this post is for you.&lt;/p&gt;

&lt;p&gt;In this post, I will talk about the method I found to speed up a tenancy test suite by 3x.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools:&lt;/strong&gt; Laravel 13, Pest 5, stancl/tenancy v4&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Create one tenant (and its database) per test file instead of one per test, wrap each test in transactions on the central and tenant connections, roll back and disconnect after each test, and drop the tenant and its database in a shutdown function once the run is over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why running tenancy tests becomes a nightmare
&lt;/h2&gt;

&lt;p&gt;In an application that uses tenancy, the test suite usually gets very slow very quickly. This happens because each tenant needs its own database, and that database must be migrated with all the tenant tables. This happens automatically, of course, if you are using a package like &lt;code&gt;stancl/tenancy&lt;/code&gt;. So when you 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="nc"&gt;Tenant&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you are not just creating a tenant row, but also building its database and running its migrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does this happen under the hood?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you create a tenant, a &lt;code&gt;TenantCreated&lt;/code&gt; event is dispatched, and multiple jobs are registered for that event. This is evident in the &lt;code&gt;TenancyServiceProvider.php&lt;/code&gt; file, where every event and its respective jobs are listed:&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="nc"&gt;Events\TenantCreated&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nc"&gt;JobPipeline&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                    &lt;span class="nc"&gt;Jobs\CreateDatabase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nc"&gt;Jobs\MigrateDatabase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&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;Events&lt;/span&gt;&lt;span class="nc"&gt;\TenantCreated&lt;/span&gt; &lt;span class="nv"&gt;$event&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;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tenant&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;shouldBeQueued&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, two jobs are dispatched: &lt;code&gt;CreateDatabase&lt;/code&gt; and &lt;code&gt;MigrateDatabase&lt;/code&gt;. You can infer each one's role from its name: one creates the database and the other migrates it. This is the part that takes time to run. If you create a tenant for each test, you will be creating and migrating a database for every test, so if you have 50 tests, that becomes 50 databases.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the jobs are marked with &lt;code&gt;shouldBeQueued()&lt;/code&gt;, but in the test environment the queue runs synchronously (&lt;code&gt;QUEUE_CONNECTION=sync&lt;/code&gt;), so the test waits for the database to be created and migrated before it continues.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Measured on 111 tests, using an i5 processor with 8GB RAM.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;th&gt;Time saved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sequential&lt;/td&gt;
&lt;td&gt;87s&lt;/td&gt;
&lt;td&gt;26s&lt;/td&gt;
&lt;td&gt;3.35x&lt;/td&gt;
&lt;td&gt;61s (70% less)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel (8 processes)&lt;/td&gt;
&lt;td&gt;45s&lt;/td&gt;
&lt;td&gt;16s&lt;/td&gt;
&lt;td&gt;2.81x&lt;/td&gt;
&lt;td&gt;29s (64% less)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is roughly a 3x speedup, or 70% less time spent running the suite. The rest of this post explains how to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare the database for each test
&lt;/h2&gt;

&lt;p&gt;An example with Pest would be:&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="nf"&gt;beforeEach&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="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;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&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="nf"&gt;tenancy&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;initialize&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;tenant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;tenancy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tenant&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Test One'&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="p"&gt;{});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Test Two'&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="p"&gt;{});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, two databases will be created. First, we will improve the code a little so we can reuse it across all our tenant tests. For that, we will use a trait, like the following:&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&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\Models\Tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;HandleTenancy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Tenant&lt;/span&gt; &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUpTenancy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&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="nf"&gt;tenancy&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;initialize&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;tenant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDownTenancy&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="nf"&gt;tenancy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tenant&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;purge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;purge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;Register the trait in the Pest file:&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="nf"&gt;pest&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;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestCase&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;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HandleTenancy&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;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Tenant'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your tests are separated from your central application tests. In my case, I use a &lt;code&gt;Tenant&lt;/code&gt; directory where I put all tests related to tenancy. Also make sure you are not using the &lt;code&gt;RefreshDatabase&lt;/code&gt; trait, as it would delete the tenant without deleting its database.&lt;/p&gt;

&lt;p&gt;Then you can do the following in your tests:&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="nf"&gt;beforeEach&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="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;setUpTenancy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

   &lt;span class="c1"&gt;// Prepare some other model&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&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="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;tearDownTenancy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Test One'&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="p"&gt;{});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Test Two'&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="p"&gt;{});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two databases are still being created, but now we have a unified place to make changes: the &lt;code&gt;HandleTenancy&lt;/code&gt; trait.&lt;/p&gt;

&lt;p&gt;If you want to improve this further, you can call the methods in the Pest file for each test, 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="nf"&gt;pest&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;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestCase&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;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HandleTenancy&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;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setUpTenancy&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;afterEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;tearDownTenancy&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;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Tenant'&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, you no longer need to call them in each test file.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to improve the speed
&lt;/h2&gt;

&lt;p&gt;As we said earlier, the problem arises because creating a tenant also creates its database and runs the migrations, which slows us down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, what should we do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reuse the tenant and its database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But how?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's easy, really. The Laravel framework already allows for this through transactions.&lt;/p&gt;

&lt;p&gt;So we need to solve the following problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of creating a tenant for each test, reuse the first tenant created.&lt;/li&gt;
&lt;li&gt;Manage connections, because if we don't, multiple database errors will arise, especially if we are using parallelization.&lt;/li&gt;
&lt;li&gt;If we reuse the tenant, we need a way to delete the tenant and its database at the end, once all the tests have completed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reusing the tenant
&lt;/h3&gt;

&lt;p&gt;In a normal Laravel tenancy application, we have two connections: the central application connection (in my case, PostgreSQL, &lt;code&gt;pgsql&lt;/code&gt;) and another for the tenant connection, named &lt;code&gt;tenant&lt;/code&gt;. For each test, the framework creates new connections. If we run the tests sequentially, this is not a problem, but when running them in parallel, multiple connections are created and quickly exhaust the number of simultaneous connections we can make to the database. In my case, with PostgreSQL, the limit is set at &lt;code&gt;97&lt;/code&gt; for regular users, and three connections are reserved for administrators, which we cannot normally use.&lt;/p&gt;

&lt;p&gt;So we need to take this into account when we create the &lt;code&gt;tearDownTenancy&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;At the beginning, our &lt;code&gt;HandleTenancy&lt;/code&gt; trait 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&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\Models\Tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;HandleTenancy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUpTenancy&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDownTenancy&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;tenancy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how we should manage the teardown of the tenancy. Instead of deleting the tenant, we roll back the database and close the connections after each test, so they don't build up and cause the problems we talked about before.&lt;/p&gt;

&lt;p&gt;Next, we will reuse the tenant. We need to create a static variable that stores the tenant's ID so we can reuse it in each test. If &lt;code&gt;$sharedTenantId&lt;/code&gt; is not null, that means we are not in the first test and the tenant should not be created. Otherwise, we create the tenant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; a static property declared in a trait belongs to each class that uses the trait, not to the whole PHP process. Pest compiles each test file into its own class, so you get one shared tenant per test file rather than one for the entire suite. That is still a big win. If you want a single tenant per process, keep the static property in a separate class and read it from the trait.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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&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\Models\Tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;HandleTenancy&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;static&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$sharedTenantId&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUpTenancy&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&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="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDownTenancy&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;tenancy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;disconnect&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;Now we should think about how to use transactions with all this logic. We can do it very simply: make sure you create the tenant before beginning the transaction.&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&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\Models\Tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;HandleTenancy&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;static&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$sharedTenantId&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUpTenancy&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&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="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&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="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;tenancy&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;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDownTenancy&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;tenancy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;disconnect&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;Now we are using transactions and managing the connections well, but we have another issue to take care of: the tenant and its database are not deleted after each test suite run. We reuse a single database per test file, but that same database is not deleted at the end, which will cause databases to build up after multiple test runs. To resolve this, PHP provides a special function called &lt;code&gt;register_shutdown_function&lt;/code&gt;, which allows us to define a function that is called before the PHP process shuts down.&lt;/p&gt;

&lt;p&gt;We will call it inside the &lt;code&gt;if&lt;/code&gt; clause in the &lt;code&gt;setUpTenancy&lt;/code&gt; method. When the function runs, the Laravel container will not be available, so we need to pass all the required values as parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUpTenancy&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&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="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&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="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nb"&gt;register_shutdown_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dropTenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;database&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;getName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'database.connections.pgsql'&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pgsql'&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;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;tenancy&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;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenant'&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;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;dropTenant&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;$tenantId&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;$databaseName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$pdo&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;PDO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"pgsql:host=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;port=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'port'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;dbname=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;]&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="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'username'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nv"&gt;$pdo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DELETE FROM tenants WHERE id = '&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$pdo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tenantId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="nv"&gt;$pdo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DROP DATABASE IF EXISTS "'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$databaseName&lt;/span&gt;&lt;span class="mf"&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STDERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Tenant cleanup failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;Now the database gets deleted after each run.&lt;/p&gt;

&lt;p&gt;After refactoring, you should end up with the following piece of 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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests&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\Models\Tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;PDO&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;Throwable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;trait&lt;/span&gt; &lt;span class="nc"&gt;HandleTenancy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;string&lt;/span&gt; &lt;span class="no"&gt;CENTRAL_CONNECTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pgsql'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;string&lt;/span&gt; &lt;span class="no"&gt;TENANT_CONNECTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tenant'&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;static&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$sharedTenantId&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUpTenancy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ensureSharedTenantExists&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CENTRAL_CONNECTION&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;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;tenancy&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;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TENANT_CONNECTION&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;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tearDownTenancy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rollBackAndDisconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TENANT_CONNECTION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;tenancy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rollBackAndDisconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CENTRAL_CONNECTION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;ensureSharedTenantExists&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&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="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$sharedTenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// stancl only needs this connection while creating the database.&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;purge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tenancy.database.tenant_host_connection_name'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Pass plain values only: the container is already gone at shutdown.&lt;/span&gt;
        &lt;span class="nb"&gt;register_shutdown_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dropTenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;database&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;getName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'database.connections.'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CENTRAL_CONNECTION&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rollBackAndDisconnect&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;$connection&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$connection&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;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$connection&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;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;dropTenant&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;$tenantId&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;$databaseName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$pdo&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;PDO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"pgsql:host=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;port=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'port'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;dbname=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;]&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="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'username'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nv"&gt;$pdo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DELETE FROM tenants WHERE id = '&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$pdo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tenantId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="nv"&gt;$pdo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DROP DATABASE IF EXISTS "'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$databaseName&lt;/span&gt;&lt;span class="mf"&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STDERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Tenant cleanup failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What about parallel runs?
&lt;/h2&gt;

&lt;p&gt;When you run the tests in parallel, each worker is its own PHP process. This means each worker creates its own shared tenant (and database), runs its tests against it, and registers its own shutdown function to clean up. Since we roll back and disconnect after every test, the number of open connections stays low, and workers never share a tenant or step on each other's data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;This approach is not a fit for every test, so keep these in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tests that depend on committed data.&lt;/strong&gt; Everything runs inside a transaction that is rolled back, so anything that needs data to be actually committed will not work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple connections and queued jobs.&lt;/strong&gt; If your code uses a different connection, or a queued job runs on another connection, it will not see the uncommitted data from the transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests for tenant creation itself.&lt;/strong&gt; If you are testing the creation or deletion of a tenant, you still need the old per-test approach, so keep those tests outside of this trait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequences do not roll back in PostgreSQL.&lt;/strong&gt; Auto-increment IDs will keep growing between tests, so never assert on a specific ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The shutdown function is not bulletproof.&lt;/strong&gt; If the process is killed (&lt;code&gt;kill -9&lt;/code&gt;, a CI timeout, and so on), &lt;code&gt;register_shutdown_function&lt;/code&gt; will not run, and a database can be left behind from time to time. It is worth cleaning those up occasionally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DROP DATABASE&lt;/code&gt; fails if connections are still open.&lt;/strong&gt; If you run into this, PostgreSQL 13 and later supports forcing the drop:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$pdo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DROP DATABASE IF EXISTS "'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$databaseName&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'" WITH (FORCE)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>laravel</category>
      <category>php</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
