<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Goran Popović</title>
    <description>The latest articles on Forem by Goran Popović (@geoligard).</description>
    <link>https://forem.com/geoligard</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1280282%2F8f22f8df-caea-45a2-99d2-9ea936284e38.jpeg</url>
      <title>Forem: Goran Popović</title>
      <link>https://forem.com/geoligard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/geoligard"/>
    <language>en</language>
    <item>
      <title>Running custom Artisan commands with Supervisor</title>
      <dc:creator>Goran Popović</dc:creator>
      <pubDate>Tue, 05 Mar 2024 00:35:25 +0000</pubDate>
      <link>https://forem.com/geoligard/running-custom-artisan-commands-with-supervisor-p1k</link>
      <guid>https://forem.com/geoligard/running-custom-artisan-commands-with-supervisor-p1k</guid>
      <description>&lt;p&gt;If you ever used &lt;code&gt;Laravel queues&lt;/code&gt; on the server, you probably came across a section in the &lt;a title="Running Supervisor" href="https://laravel.com/docs/10.x/queues#supervisor-configuration" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; about needing to run a &lt;code&gt;Supervisor&lt;/code&gt; on your server in order to keep your queue processes going in case they are stopped for whatever reason. What you would typically need to do is &lt;a title="Configuring Supervisor" href="https://laravel.com/docs/10.x/queues#configuring-supervisor" rel="noopener noreferrer"&gt;configure&lt;/a&gt; the Supervisor to run the &lt;code&gt;queue:work&lt;/code&gt; Artisan command.&lt;/p&gt;

&lt;p&gt;I won't get too much into detail now on how different Artisan commands regarding queues are functioning behind the scenes, but if we take a closer look at the &lt;code&gt;queue:work&lt;/code&gt; command, for example, essentially what is going on is that the jobs are being run inside a &lt;code&gt;while loop&lt;/code&gt;, and if certain conditions are met, the worker will be stopped and the command will exit. After that, the Supervisor will run it again. &lt;/p&gt;

&lt;p&gt;Here is an example of what a trimmed-down and simplified version of that loop might look like (taken from the &lt;code&gt;daemon&lt;/code&gt; function inside the &lt;code&gt;Illuminate/Queue/Worker.php&lt;/code&gt; file):&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;while&lt;/span&gt; &lt;span class="p"&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="c1"&gt;// Get the next job from the queue.&lt;/span&gt;
    &lt;span class="nv"&gt;$job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getNextJob&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;manager&lt;/span&gt;&lt;span class="o"&gt;-&amp;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;$connectionName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$queue&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Run the job if needed.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$jobsProcessed&lt;/span&gt;&lt;span class="o"&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;runJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$connectionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the worker status.&lt;/span&gt;
    &lt;span class="nv"&gt;$status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stopIfNecessary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;$options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$lastRestart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$startTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$jobsProcessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$job&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Stop the worker if needed.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;is_null&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$options&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;Custom command&lt;/h2&gt;



&lt;p&gt;Now, if we were to take that example as a starting point, we could create our own command that could be run indefinitely (if needed) with the help of the Supervisor.&lt;/p&gt;

&lt;p&gt;Let's say we created an app for the Handyman Tools Store. The user selects an item, fills in the necessary data, and submits an order for a specific tool. As soon as the order is created, it is automatically set to a &lt;code&gt;pending&lt;/code&gt; status. To be able to monitor that kind of order and include some logic of our own, we could create a custom Artisan command that would check for all pending orders from time to time, and if any are found, we could dispatch a Laravel job that would further process that order (maybe check if the tool is in stock, can it be shipped to the user's location, and send an email to the user about that order status).&lt;/p&gt;

&lt;p&gt;Here is an example of such a command:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Console\Commands&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\Console\Command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MonitorOrders&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * The name and signature of the console command.
     *
     * @var string
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'orders:monitor'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * The console command description.
     *
     * @var string
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Monitor pending tool orders and dispatch them for processing.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Execute the console command.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&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="c1"&gt;// Get all the orders with the pending status&lt;/span&gt;
            &lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// If there are no orders with a pending status wait for a second then check again&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;doesntExist&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Dispatch the order for further processing to a job&lt;/span&gt;
                &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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;onQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&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;onConnection&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="p"&gt;}&lt;/span&gt;

            &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Potentially take a break between runs&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;Option for max jobs&lt;/h2&gt;



&lt;p&gt;What if we wanted to specify the number of jobs that should be processed,  similar to the &lt;a title="Specify number of jobs" href="https://laravel.com/docs/10.x/queues#processing-a-specified-number-of-jobs" rel="noopener noreferrer"&gt;--max-jobs &lt;/a&gt;flag? If the maximum number of jobs is reached the worker could exit and release any memory that may have accumulated. To achieve that, we could introduce a new option to our command, like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * The name and signature of the console command.
 *
 * @var string
 */&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'orders:monitor
                        {--max-jobs=100 : The number of jobs to process before stopping}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Then, in our &lt;code&gt;handle&lt;/code&gt; method, we can set the initial number of the processed jobs and get the maximum number from the option we defined previously:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * Execute the console command.
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$jobsProcessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$maxJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'max-jobs'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&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="c1"&gt;// Get all the orders with the pending status&lt;/span&gt;
            &lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;As soon as the job is sent to be processed, we will increment the &lt;code&gt;$jobsProcessed&lt;/code&gt; variable and check if the worker should exit. If that is the case, we would just return a default success exit code (&lt;code&gt;0&lt;/code&gt;). After the worker exits, the Supervisor will restart it again.&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;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Dispatch the order for further processing to a job&lt;/span&gt;
    &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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;onQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&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;onConnection&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="c1"&gt;// Increase the number of processed jobs&lt;/span&gt;
    &lt;span class="nv"&gt;$jobsProcessed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Stop the command if the number of jobs reaches the maximum number set&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jobsProcessed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nv"&gt;$maxJobs&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Success&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;Restarting the command manually&lt;/h2&gt;



&lt;p&gt;When you update the code inside of the command and deploy it to the server, the change won't be reflected until the Supervisor has had a chance to actually restart the worker. To resolve that issue, you could restart the Supervisor itself on deploy (and reread the configuration file if you updated it):&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;supervisorctl reread
&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl update
&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Or better yet, we could create another custom command that will cause our initial &lt;code&gt;orders:monitor&lt;/code&gt; command to exit, again allowing the Supervisor to restart it. We could do that by using the built-in &lt;code&gt;Cache&lt;/code&gt; functionality, similar to how the &lt;a title="Queue restart" href="https://laravel.com/docs/10.x/queues#queue-workers-and-deployment" rel="noopener noreferrer"&gt;queue:restart&lt;/a&gt; command does it.&lt;/p&gt;

&lt;p&gt;Here is an example of how our &lt;code&gt;orders:restart&lt;/code&gt; command could look like:&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;App\Console\Commands&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\Console\Command&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\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RestartOrders&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * The console command name.
     *
     * @var string
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'orders:restart'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * The console command description.
     *
     * @var string
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Restart orders monitor command after the current job.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Execute the console command.
     *
     * @return void
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders:restart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Now, we need to amend our &lt;code&gt;orders:monitor&lt;/code&gt; command to check if the &lt;code&gt;orders:restart&lt;/code&gt; has been triggered. Make sure to include the &lt;code&gt;Cache&lt;/code&gt; facade first:&lt;/p&gt;



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

&lt;/div&gt;





&lt;p&gt;And then update the loop like so: &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;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Dispatch the order for further processing to a job&lt;/span&gt;
    &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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;onQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&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;onConnection&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="c1"&gt;// Increase the number of processed jobs&lt;/span&gt;
    &lt;span class="nv"&gt;$jobsProcessed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Stop the command if the number of jobs reaches the maximum number set&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jobsProcessed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nv"&gt;$maxJobs&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Success&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the restart command value from the cache&lt;/span&gt;
    &lt;span class="nv"&gt;$restartCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders:restart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Stop the command if needed and remove the entry from the cache&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$restartCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders:restart'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Success&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;That's it. Now you can add the  &lt;code&gt;orders:restart&lt;/code&gt; command to your deploy procedure, and you won't have to worry about any changes in your code not being reflected.&lt;/p&gt;

&lt;p&gt;As per usual, you can check out the &lt;a title="Gist example" href="https://gist.github.com/goran-popovic/2aac3715ce25588941e23889b44c88ba" rel="noopener noreferrer nofollow"&gt;Gist&lt;/a&gt; for source code.&lt;/p&gt;



&lt;h2&gt;Supervisor configuration&lt;/h2&gt;



&lt;p&gt;What's left for us to do is add a supervisor configuration file, named, for example, &lt;code&gt;monitor-orders.conf&lt;/code&gt;, in the &lt;code&gt;/etc/supervisor/conf.d&lt;/code&gt; directory (location of the directory in most cases) that starts (and restarts, if needed) our command, which will in turn continually monitor for any pending tool orders.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;program:monitor-orders]
&lt;span class="nv"&gt;process_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%&lt;span class="o"&gt;(&lt;/span&gt;program_name&lt;span class="o"&gt;)&lt;/span&gt;s_%&lt;span class="o"&gt;(&lt;/span&gt;process_num&lt;span class="o"&gt;)&lt;/span&gt;02d
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php /home/forge/app.com/artisan orders:monitor &lt;span class="nt"&gt;--max-jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;stopasgroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;killasgroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;forge
&lt;span class="nv"&gt;numprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8
&lt;span class="nv"&gt;redirect_stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;stdout_logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/forge/app.com/worker.log
&lt;span class="nv"&gt;stopwaitsecs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;The setup above uses an example from the documentation, but you may not need to spawn 8 processes (&lt;code&gt;numprocs&lt;/code&gt;), depending on the number of orders you receive. The path to the Artisan command on your server might be completely different; it could be something like &lt;code&gt;php /var/www/my-website.com/artisan&lt;/code&gt; so make sure to double check that as well as the &lt;code&gt;user&lt;/code&gt; value and the path to the &lt;code&gt;worker.log&lt;/code&gt; file.&lt;/p&gt;



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



&lt;p&gt;We made it. Following the basic example of the &lt;code&gt;queue:work&lt;/code&gt; command and the instructions from the Laravel &lt;code&gt;documentation&lt;/code&gt;, we were able to create our own custom command that will monitor for pending orders in our app for Handyman Tools. With the help of the Supervisor, the command will be run automatically in the background and restarted if anything unexpected happens. &lt;/p&gt;

&lt;p&gt;Of course, the examples above are just a start; you could build a lot more complex logic around them; you could introduce more conditions by which the pending orders should be processed or not; you could add other useful options and arguments; and so on. &lt;/p&gt;

&lt;p&gt;All in all, I hope this article helped you get a better understanding of Artisan commands that are run with the help of the Supervisor.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enjoyed the article? Consider subscribing to my &lt;a href="https://geoligard.com/newsletter"&gt;newsletter&lt;/a&gt; for future updates.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geoligard.com/running-custom-artisan-commands-with-supervisor"&gt;geoligard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>supervisor</category>
      <category>console</category>
      <category>artisan</category>
    </item>
    <item>
      <title>EmailOctopus SDKs for PHP and Laravel</title>
      <dc:creator>Goran Popović</dc:creator>
      <pubDate>Tue, 05 Mar 2024 00:28:41 +0000</pubDate>
      <link>https://forem.com/geoligard/emailoctopus-sdks-for-php-and-laravel-54f6</link>
      <guid>https://forem.com/geoligard/emailoctopus-sdks-for-php-and-laravel-54f6</guid>
      <description>&lt;p&gt;When I wanted to start a newsletter and send regular updates to the subscribers of my blog, and since I didn't have that much experience with sending email campaigns, I needed a service that is simple to use, easy to integrate, has a good reputation, and has transparent and affordable pricing plans (possibly with a free plan with essential options to try things out and get started).&lt;/p&gt;

&lt;p&gt;I went through some usual (and some less usual) suspects like Mailchimp, MailerLite, ConvertKit, Brevo, Campaign Monitor, and so on. But none of those really clicked for me at first glance, they were either too advanced and with too many options for my needs, the prices were too steep, or some of their free and starter plans were too restrictive.&lt;/p&gt;

&lt;p&gt;That's when I discovered &lt;code&gt;EmailOctopus&lt;/code&gt;. I found it to be the most appropriate email marketing platform for my current needs. I really like the simplicity of their dashboard and the UI. They have a well-maintained and informative knowledge base where I was able to find anything that was possibly unclear to me. Later on, I discovered that their support is also pretty responsive. They have a very decent API as well, which is easy to use, and they offer a really generous free plan to get you started.&lt;/p&gt;

&lt;p&gt;They achieve their affordable pricing by focusing on a single service—providing you with a way to send marketing emails. So, for example, that means that they currently don't offer an option to use a SMTP connection to set up transactional emails. All of the emails you plan on sending must be created and sent through their dashboard.&lt;/p&gt;

&lt;p&gt;This is by no means a sponsored article, I'm speaking from my own experience and related to my particular use case. Your situation might be completely different, and you are free to stick to any service that suits your needs best.&lt;/p&gt;

&lt;p&gt;That being said, EmailOctopus is by no means perfect; there are improvements that could be made (i.e., IDs of different resources needed for the API could be more prominent), but I found it to be near-perfect in my scenario.&lt;/p&gt;

&lt;p&gt;Anyway, as I browsed through different integrations and community-maintained libraries and SDKs, I noticed there isn't a &lt;code&gt;PHP&lt;/code&gt; or &lt;code&gt;Laravel-based&lt;/code&gt; API wrapper, so I thought it would be helpful to create a &lt;code&gt;PHP API client&lt;/code&gt; that will allow others to easily communicate with the &lt;code&gt;EmailOctopus's API&lt;/code&gt; in their PHP applications. Using the packages I created, you can easily subscribe or unsubscribe users to your newsletter, trigger automations, and view various data about your campaigns.&lt;/p&gt;



&lt;h2&gt;PHP SDK&lt;/h2&gt;



&lt;p&gt;If you want to interact with the API in a framework-agnostic way, you can install the Email Octopus SDK for PHP using Composer:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require goran-popovic/email-octopus-php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Next, in order to get started you would need to create an &lt;a title="Email Octopus API key" href="https://help.emailoctopus.com/article/165-how-to-create-and-delete-api-keys" rel="noopener noreferrer"&gt;Email Octopus API key&lt;/a&gt; by following the instructions from their knowledge base.&lt;/p&gt;

&lt;p&gt;You could store your API key in an &lt;code&gt;.env&lt;/code&gt; file, like this for example:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;EMAIL_OCTOPUS_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Then you can instantiate the &lt;code&gt;client&lt;/code&gt; easily with one or two lines of code, like so:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'EMAIL_OCTOPUS_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EmailOctopus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;The most usual use case will probably be to &lt;code&gt;add/subscribe&lt;/code&gt; a contact to your list when someone fills out the newsletter form on your website. This can be easily done with a few lines of code by providing a &lt;code&gt;list ID&lt;/code&gt; and the &lt;code&gt;email address&lt;/code&gt; of the contact:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lists&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;createContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'email_address'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'goran.popovic@geoligard.com'&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;The ID of the list (&lt;code&gt;00000000-0000-0000-0000-000000000000&lt;/code&gt;) can be found when editing the list in the &lt;code&gt;settings&lt;/code&gt; tab, or in the URL in the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a title="List ID" href="https://geoligard.com/storage/posts/February2024/list-settings.png"&gt;&lt;img title="List ID" src="https://res.cloudinary.com/practicaldev/image/fetch/s--fXZk_I3W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/February2024/list-settings.png" alt="List ID" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IDs of other resources can also be found directly in the &lt;code&gt;URL&lt;/code&gt;, like the &lt;code&gt;automation ID&lt;/code&gt;, &lt;code&gt;campaign ID&lt;/code&gt;, &lt;code&gt;contact ID&lt;/code&gt;, and so on. So be sure to keep an eye on those.&lt;/p&gt;

&lt;p&gt;If the contact is already subscribed to your list, or if a contact attempts to re-subscribe, the &lt;code&gt;createContact&lt;/code&gt; API call will fail since that email already exists.&lt;/p&gt;

&lt;p&gt;In order to handle that use case, you could check if the contact is already subscribed by trying to get the contact first, and if the contact exists, update it, and if it doesn't exist, create a new one:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lists&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;getContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&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;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// contact doesn't exist&lt;/span&gt;
    &lt;span class="c1"&gt;// create contact&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lists&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;createContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'email_address'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'goran.popovic@geoligard.com'&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// update existing contact&lt;/span&gt;
    &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lists&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;updateContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'goran.popovic@geoligard.com'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'SUBSCRIBED'&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;or you could just try to create a contact every time, and if that fails with a specific error code, then update it:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lists&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;createContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'email_address'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'goran.popovic@geoligard.com'&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// creating a contact failed&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'code'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'MEMBER_EXISTS_WITH_EMAIL_ADDRESS'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// update existing contact&lt;/span&gt;
    &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lists&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;updateContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'goran.popovic@geoligard.com'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'SUBSCRIBED'&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;Check out the repo and the docs for all of the remaining routes on &lt;a title="PHP SDK" href="https://github.com/goran-popovic/email-octopus-php" rel="noopener noreferrer nofollow"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;



&lt;h2&gt;Laravel SDK&lt;/h2&gt;



&lt;p&gt;Now, when it comes to the Email Octopus SDK for Laravel, as you may have guessed, it's basically another wrapper around the PHP version of the SDK, and all of the routes that are available in the PHP SDK are also available in the Laravel one. The Laravel package will allow you to easily interact with the &lt;code&gt;API Client&lt;/code&gt; through the use of &lt;code&gt;Facades&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, we should install the package:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require goran-popovic/email-octopus-laravel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Please note that if you are using the &lt;code&gt;Laravel SDK&lt;/code&gt;, you don't need to install the &lt;code&gt;PHP SDK&lt;/code&gt; separately, since it is already included as a dependency for the Laravel package.&lt;/p&gt;

&lt;p&gt;Now that we have installed the package, you can optionally publish the config files with this command:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"email-octopus-config"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Like I mentioned already in the previous section, make sure to get your &lt;a title="Email Octopus API key" href="https://help.emailoctopus.com/article/165-how-to-create-and-delete-api-keys" rel="noopener noreferrer"&gt;Email Octopus API key&lt;/a&gt; and add it to your &lt;code&gt;.env&lt;/code&gt; file:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;EMAIL_OCTOPUS_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Now, just just need to use the Facade in your file:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GoranPopovic\EmailOctopus\Facades\EmailOctopus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;and you can perform the same operations as with the PHP SDK through the Facade, like so:&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;EmailOctopus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;lists&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;createContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'00000000-0000-0000-0000-000000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'email_address'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'goran.popovic@geoligard.com'&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;Check out the repo of the &lt;a title="Laravel SDK" href="https://github.com/goran-popovic/email-octopus-laravel" rel="noopener noreferrer nofollow"&gt;Laravel SDK&lt;/a&gt; for further instructions and details.&lt;/p&gt;



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



&lt;p&gt;As I already mentioned, EmailOctopus has worked well for me so far, and I'm pretty satisfied with their service. If you ever decide to try them out for yourself, I hope both SDKs will prove to be useful assets when interacting with the EmailOctopus's API from your PHP applications.&lt;/p&gt;

&lt;p&gt;For any bugs and improvements, feel free to create a pull request or open an issue on GitHub.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enjoyed the article? Consider subscribing to my &lt;a href="https://geoligard.com/newsletter"&gt;newsletter&lt;/a&gt; for future updates.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geoligard.com/emailoctopus-sdks-for-php-and-laravel"&gt;geoligard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>api</category>
      <category>newsletter</category>
    </item>
    <item>
      <title>Send, validate, and store Base64 files with Laravel</title>
      <dc:creator>Goran Popović</dc:creator>
      <pubDate>Tue, 05 Mar 2024 00:25:35 +0000</pubDate>
      <link>https://forem.com/geoligard/send-validate-and-store-base64-files-with-laravel-2e48</link>
      <guid>https://forem.com/geoligard/send-validate-and-store-base64-files-with-laravel-2e48</guid>
      <description>&lt;p&gt;Laravel makes it easy to send and upload files, at least when it comes to handling binary files. You would typically send binary files using an HTML form with a &lt;code&gt;multipart/form-data&lt;/code&gt; encoding type. Inside your controller, you can simply get the file like this: &lt;code&gt;$request-&amp;gt;file('image')&lt;/code&gt; and save it on the disk. The file you receive returns an instance of the &lt;code&gt;Illuminate\Http\UploadedFile&lt;/code&gt; class, which provides you with a lot of handy methods to manipulate the received file further. Pretty easy and straightforward, right?&lt;/p&gt;

&lt;p&gt;But what if you want to send and receive files with JSON requests? Things get a bit tricky there since JSON requests can't transport binary data. You would have to convert your file to a Base64 string and then process it within your application to extract the necessary data. Let's see how we can do that.&lt;/p&gt;



&lt;h2 id="covert-base64"&gt;Converting the image to Base64&lt;/h2&gt;



&lt;p&gt;First, let's see how we can convert an image of our choosing to a Base64 string. This can be of course done programmatically, but for simplicity, we are just going to upload a simple &lt;code&gt;PNG&lt;/code&gt; image on a &lt;a title="Convert image" href="https://base64.guru/converter/encode/image" rel="noopener noreferrer nofollow"&gt;Base64 Guru website&lt;/a&gt; (you can, of course, use any other), and save the output for later use. &lt;/p&gt;

&lt;p&gt;Here is an example of what that might look like:&lt;/p&gt;

&lt;p&gt;&lt;a title="Converting an image" href="https://geoligard.com/storage/posts/January2024/covert-file.png"&gt;&lt;img title="Converting an image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--yjKhoMH---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/January2024/covert-file.png" alt="Converting an image" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2 id="postman-example"&gt;Postman example&lt;/h2&gt;



&lt;p&gt;Now, before we proceed, let's see a complete example of that kind of request in &lt;code&gt;Postman&lt;/code&gt;. So, in short, we will be sending a raw JSON POST request with a single &lt;code&gt;image&lt;/code&gt; param, and in return, we will just get the URL of the newly stored file.&lt;/p&gt;

&lt;p&gt;That might look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a title="Postman example" href="https://geoligard.com/storage/posts/January2024/base64-postman-example_1.png"&gt;&lt;img title="Postman example" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Oq62gTx3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/January2024/base64-postman-example_1.png" alt="Postman example" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2 id="initial-setup"&gt;Initial setup&lt;/h2&gt;



&lt;p&gt;Ok, we know what to expect. Let's create a controller and a route to handle the request.&lt;/p&gt;



&lt;pre&gt;&lt;code&gt;php artisan make:controller Base64Controller&lt;/code&gt;&lt;/pre&gt;



&lt;pre&gt;&lt;code&gt;// api.php

use App\Http\Controllers\Base64Controller;

Route::post('base64', [Base64Controller::class, 'index']);&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;So far, so good. Our &lt;code&gt;base64&lt;/code&gt; route will target the &lt;code&gt;index&lt;/code&gt; method inside of the &lt;code&gt;Base64Controller&lt;/code&gt;. Next, we will add some basic code to handle the JSON request in the &lt;code&gt;index&lt;/code&gt; method.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Base64Controller.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&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\Http\File&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\Http\UploadedFile&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\Arr&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\Facades\Storage&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\Facades\Validator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Base64Controller&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&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;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;request&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;isJson&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;request&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;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nv"&gt;$base64Image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;request&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;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$tmpFileObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validateBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base64Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jpeg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gif'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Invalid image format.'&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;415&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nv"&gt;$storedFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;storeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tmpFileObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$storedFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Something went wrong, the file was not stored.'&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="s1"&gt;'image_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$storedFilePath&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Invalid request.'&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;We are first checking if we are dealing with a JSON request and if the image parameter is not empty. We then validate and upload the image. In the end, we return the URL of the newly uploaded file. Simple enough, right? You may have noticed that we have defined two helper functions, &lt;code&gt;validateBase64&lt;/code&gt; and &lt;code&gt;storeFile&lt;/code&gt; that are validating and storing the file for us. Time to define them.&lt;/p&gt;



&lt;h2 id="helper-functions"&gt;Helper functions&lt;/h2&gt;



&lt;p&gt;The first function (&lt;code&gt;validateBase64&lt;/code&gt;) will just check if the &lt;code&gt;image&lt;/code&gt; param is a Base64 representation of an image. It will accept a Base64 string and an array of allowed mime types. If the validation doesn't fail, a temporary file object will be returned. The function is mostly based on this &lt;a title="Validate base64" href="https://stackoverflow.com/questions/51419310/validating-base64-image-laravel/52914093#52914093" rel="noopener noreferrer nofollow"&gt;Stack Overflow answer&lt;/a&gt;.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Base64Controller.php&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Validate base64 content.
 *
 * @see https://stackoverflow.com/a/52914093
 */&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;validateBase64&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;$base64data&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;$allowedMimeTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// strip out data URI scheme information (see RFC 2397)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;';base64'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(,&lt;/span&gt; &lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(,&lt;/span&gt; &lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// strict mode filters for non-base64 alphabet characters&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;base64_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&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;===&lt;/span&gt; &lt;span class="kc"&gt;false&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// decoding and then re-encoding should not change the data&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;base64_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nv"&gt;$base64data&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$fileBinaryData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;base64_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base64data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// temporarily store the decoded data on the filesystem to be able to use it later on&lt;/span&gt;
    &lt;span class="nv"&gt;$tmpFileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tempnam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sys_get_temp_dir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'medialibrary'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tmpFileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fileBinaryData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$tmpFileObject&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;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tmpFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// guard against invalid mime types&lt;/span&gt;
    &lt;span class="nv"&gt;$allowedMimeTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arr&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$allowedMimeTypes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// if there are no allowed mime types, then any type should be ok&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;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$allowedMimeTypes&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;$tmpFileObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the mime types&lt;/span&gt;
    &lt;span class="nv"&gt;$validation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Validator&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$tmpFileObject&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mimes:'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$allowedMimeTypes&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fails&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="kc"&gt;false&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;$tmpFileObject&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;Great, now let's define the second helper function that will process the temporary file object that was returned from the previous function and upload the file.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Base64Controller.php&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Store the temporary file object
 */&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;storeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;File&lt;/span&gt; &lt;span class="nv"&gt;$tmpFileObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$tmpFileObjectPathName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tmpFileObject&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPathname&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$file&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;UploadedFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;$tmpFileObjectPathName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$tmpFileObject&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFilename&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nv"&gt;$tmpFileObject&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMimeType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$storedFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/base64'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'disk'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nb"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tmpFileObjectPathName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// delete temp file&lt;/span&gt;

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

&lt;/div&gt;





&lt;p&gt;And just like that, we are done. If you would like to copy the whole code from &lt;code&gt;Base64Controller.php&lt;/code&gt; head out to &lt;a title="Gist example" href="https://gist.github.com/goran-popovic/d5af7366af2a7f6f9b04ebc4a48a4e7e" rel="noopener noreferrer nofollow"&gt;Gist&lt;/a&gt;.&lt;/p&gt;



&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;



&lt;p&gt;Today we made a few great functions that would allow us to accept, validate, and store Base64 encoded files. In our example, we were validating images, but you can use these functions for other types of files as well, like &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt;, and so on. Also, for simplicity, all of the code was added to the same controller, but you could also move these functions to your custom helper function file, or create a trait, and then easily reuse them whenever you need to process a Base64 file.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enjoyed the article? Consider subscribing to my &lt;a href="https://geoligard.com/newsletter"&gt;newsletter&lt;/a&gt; for future updates.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geoligard.com/send-validate-and-store-base64-files-with-laravel"&gt;geoligard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>base64</category>
      <category>json</category>
    </item>
    <item>
      <title>Interact with Docker containers without leaving PhpStorm</title>
      <dc:creator>Goran Popović</dc:creator>
      <pubDate>Tue, 05 Mar 2024 00:19:24 +0000</pubDate>
      <link>https://forem.com/geoligard/interact-with-docker-containers-without-leaving-phpstorm-1o20</link>
      <guid>https://forem.com/geoligard/interact-with-docker-containers-without-leaving-phpstorm-1o20</guid>
      <description>&lt;p&gt;In the previous article, I wrote about a particular setup you can use to configure Docker on WSL through SSH for performance boosts and the ability to keep using your native Windows apps without having to move them to WSL: &lt;a title="Docker on WSL with PhpStorm - Best of both worlds" href="https://dev.to/geoligard/docker-on-wsl-with-phpstorm-best-of-both-worlds-194f"&gt;Docker on WSL with PhpStorm - Best of both worlds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article, we are going to see how we can use a PHP CLI interpreter from our Docker compose file and, finally, how we can interact with containers within PhpStorm. One thing to note is that I'm using Windows 10 and Docker Desktop, which is integrated with WSL, and on WSL, I'm running my project with Laravel Sail, but general guidelines from this post can be applied to any OS.&lt;/p&gt;



&lt;h2 id="requirements"&gt;Requirements&lt;/h2&gt;



&lt;p&gt;First things first, in order for the following setup to work, make sure that the &lt;code&gt;Docker&lt;/code&gt; and &lt;code&gt;PHP Docker&lt;/code&gt; plugins are enabled in PhpStorm. These plugins are bundled and enabled by default, but if you disabled them for any reason, now would be a good time to re-enable them. Also, make sure that your Docker containers are up and running.&lt;/p&gt;



&lt;h2 id="setup-cli-interpreter"&gt;Setting up the CLI interpreter&lt;/h2&gt;



&lt;p&gt;Now, inside PhpStorm, go to &lt;code&gt;File &amp;gt; Settings &amp;gt; PHP&lt;/code&gt; and add a new CLI interpreter by clicking on the ellipsis (...): &lt;/p&gt;

&lt;p&gt;&lt;a title="PHP settings" href="https://geoligard.com/storage/posts/December2023/php-settings.png"&gt;&lt;img title="PHP settings" src="https://res.cloudinary.com/practicaldev/image/fetch/s--oARXkVFR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/php-settings.png" alt="PHP settings" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To configure the CLI interpreter from the Docker compose file, click the plus icon to add a new interpreter, select &lt;code&gt;From Docker, Vagrant, VM, WSL, Remote...&lt;/code&gt; and go to the &lt;code&gt;Docker Compose&lt;/code&gt; tab: &lt;/p&gt;

&lt;p&gt;&lt;a title="Add remote interpreter" href="https://geoligard.com/storage/posts/December2023/add-remote-interpreter.png"&gt;&lt;img title="Add remote interpreter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--isB4GeWq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/add-remote-interpreter.png" alt="Add remote interpreter" width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2 id="setup-docker-connection"&gt;Setting up a Docker server&lt;/h2&gt;



&lt;p&gt;While in that tab, we are going to create a new Docker server configuration in another modal. Click on the &lt;code&gt;New...&lt;/code&gt; button, add a server name, and choose a connection. &lt;/p&gt;

&lt;p&gt;There are several connections to choose from, and depending on your OS, for the first option, you might get something like &lt;code&gt;Docker for Windows&lt;/code&gt;, &lt;code&gt;Docker for Mac&lt;/code&gt;, or &lt;code&gt;Unix socket&lt;/code&gt; (for Linux). In this example, I'm going to use &lt;code&gt;WSL&lt;/code&gt;, but if you have another setup or, for some reason, WSL integration doesn't work for you in the end, remember to return to this modal (I'll explain where to find it later on) and change the connection. &lt;code&gt;Docker for Windows&lt;/code&gt; and &lt;code&gt;SSH&lt;/code&gt; (you can refer to the previous article on how to setup an SSH connection in PhpStorm) options worked well for me in some other cases. Here is what that setup might look like in the end: &lt;/p&gt;

&lt;p&gt;&lt;a title="Docker server setup" href="https://geoligard.com/storage/posts/December2023/docker-server.png"&gt;&lt;img title="Docker server setup" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Iu5T196Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/docker-server.png" alt="Docker server setup" width="608" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you save all of the settings in the Docker server connection modal, we are back in the &lt;code&gt;Docker compose&lt;/code&gt; tab of the CLI interpreter. &lt;/p&gt;

&lt;p&gt;What is left in our current modal is to select the local &lt;code&gt;docker-compose.yml&lt;/code&gt; file, which is in my case in the root of my project, and select a service/container that is running PHP, which is &lt;code&gt;laravel.test&lt;/code&gt; by default when using Laravel Sail. Save all of the settings, and you should be good to go. &lt;/p&gt;

&lt;p&gt;The Services tab should open automatically, and there you can connect to the Docker and, for example, access the PHP container and interact with it: &lt;/p&gt;

&lt;p&gt;&lt;a title="Docker services" href="https://geoligard.com/storage/posts/December2023/docker-terminal.png"&gt;&lt;img title="Docker services" src="https://res.cloudinary.com/practicaldev/image/fetch/s--s39CryeM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/docker-terminal.png" alt="Docker services" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2 id="troubleshooting"&gt;Troubleshooting&lt;/h2&gt;



&lt;p&gt;If you can't connect to Docker containers, or the PHP CLI interpreter is telling you it can't find the PHP executable, go back to Docker server settings (&lt;code&gt;File &amp;gt; Settings &amp;gt; Build, Execution, Deployment &amp;gt; Deployment &amp;gt; Docker&lt;/code&gt;) and try using another connection, like &lt;code&gt;SSH&lt;/code&gt; or &lt;code&gt;Docker for Windows/Mac/Unix socket&lt;/code&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a title="Update Docker server" href="https://geoligard.com/storage/posts/December2023/docker-server-update.png"&gt;&lt;img title="Update Docker server" src="https://res.cloudinary.com/practicaldev/image/fetch/s--GxPhnGgm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/docker-server-update.png" alt="Update Docker server" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;



&lt;p&gt;We made it! We are able to setup PhpStorm integration with Docker to easily start and stop Docker containers and access their terminals directly from the services tab inside PhpStorm. Also, this setup allowed us to use the PHP from our Docker container as our project's PHP CLI interpreter. Pretty neat!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enjoyed the article? Consider subscribing to my &lt;a href="https://geoligard.com/newsletter"&gt;newsletter&lt;/a&gt; for future updates.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geoligard.com/interact-with-docker-containers-without-leaving-phpstorm"&gt;geoligard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>windows</category>
      <category>wsl</category>
      <category>phpstorm</category>
    </item>
    <item>
      <title>Docker on WSL with PhpStorm - Best of both worlds</title>
      <dc:creator>Goran Popović</dc:creator>
      <pubDate>Tue, 05 Mar 2024 00:11:52 +0000</pubDate>
      <link>https://forem.com/geoligard/docker-on-wsl-with-phpstorm-best-of-both-worlds-194f</link>
      <guid>https://forem.com/geoligard/docker-on-wsl-with-phpstorm-best-of-both-worlds-194f</guid>
      <description>&lt;p&gt;Most of you are aware that using Docker locally on Windows with Hyper-V enabled can be really slow for most Laravel or any other type of project. Sure, that kind of setup is much simpler; your project files are on your hard drive, and you are running Docker and any additional tools that your project depends on natively inside your Windows OS. Hey, I've been using Docker that way for a while and actually got used to 5-8 second page load times. I know, that's pretty bad. Your project and load times might not be as big, but they could still be improved.&lt;/p&gt;

&lt;p&gt;Once, I tried running my projects inside WSL, but that introduced other issues, the main one being that my files were taking too long to be indexed both by Git and PhpStorm. So for the files to be indexed properly, I would have had to install Git, Sourcetree, and possibly PhpStorm inside WSL. That seemed like too much hassle, and I would have needed a decent alternative to Sourcetree since it isn't available for Linux systems.&lt;/p&gt;

&lt;p&gt;So I gave up for a while. Recently, those load times were starting to get bigger and bigger from time to time, so I decided to search for alternatives again to improve performance. Obviously, I didn't want to give up on Docker since it provides me with an environment that is the closest to the setup on the server we've been using. While XAMPP, Laragon, or WAMP might be good alternatives for certain types of projects, I started digging deeper into using Docker with WSL. So I came across this &lt;a title="Docker on WSL2" href="https://www.createit.com/blog/slow-docker-on-windows-wsl2-how-to-improve-performance/" rel="noopener noreferrer"&gt;article&lt;/a&gt;. The author suggested using PhpStorm's automatic deployment feature to sync files from Windows's filesystem to the ones on WSL through SSH. At first, that setup seemed a bit over the top, since an alternative might have been to use the built-in remote interpreter of PHPStorm to directly access WSL's filesystem (\$wsl), but when I tried it, that proved to be slow as well, and it didn't resolve my Sourcetree issue, so I decided to give it a go.&lt;/p&gt;

&lt;p&gt;So, while you may refer to the article above for a summary, I'll describe a bit more in depth the steps I took to perform that kind of setup, what modifications I made, and what problems you might expect. &lt;/p&gt;



&lt;h2 id="initial-setup"&gt;Initial setup&lt;/h2&gt;



&lt;p&gt;I've been using Windows 10 with 16GB of RAM, Docker for Windows v4.15.0, and PhpStorm v2023, but this kind of setup should work on Windows 11 and other newer versions of Docker that support WSL integration. In the final implementation, I was able to reduce the page load times &lt;code&gt;from 5-8s to 200-300ms&lt;/code&gt;, which was a significant improvement. At the same time, I eliminated any issues with the performance of my other tools, like PhpStorm and Sourcetree. But this approach does come with some caveats, which we will explore a bit later.&lt;/p&gt;

&lt;p&gt;Before we start, please note that this setup requires you to use PhpStorm as your IDE or a similar tool from JetBrains. Other tools might have similar functionalities you can use, but they won't be addressed in this article.&lt;/p&gt;



&lt;h2 id="installing-wsl"&gt;Installing WSL and Docker setup&lt;/h2&gt;



&lt;p&gt;The first step is to install WSL2 and set up Docker to run inside it. If you don't have WSL2 installed already, you can follow the instructions from &lt;a title="Install WSL2" href="https://learn.microsoft.com/en-us/windows/wsl/install" rel="noopener noreferrer"&gt;Microsoft's website&lt;/a&gt;. On that link, you can also read about additional helpful commands.&lt;/p&gt;

&lt;p&gt;The most important things to note here are that you can install WSL by running &lt;code&gt;wsl --install&lt;/code&gt; or update an existing installation by running &lt;code&gt;wsl --update&lt;/code&gt;. Currently, the latest version is Ubuntu 22.04.2 LTS. Use &lt;code&gt;wsl -l -v&lt;/code&gt; to check your WSL version, and you should set the default WSL version to WSL 2 with &lt;code&gt;wsl --set-default-version 2&lt;/code&gt;. When typing just 'wsl', you should be able to access the distribution, and you can shut it down with &lt;code&gt;wsl --shutdown&lt;/code&gt;. You will be asked to create a username and password when you launch Ubuntu for the first time; make sure to save them as we are going to use them later on.&lt;/p&gt;

&lt;p&gt;After WSL is installed, restart your PC, finish the installation, then run Docker, go to &lt;code&gt;Settings &amp;gt; General&lt;/code&gt;, and enable the &lt;code&gt;Use the WSL 2 based engine&lt;/code&gt; option. Next, go to resources and enable integration for your distribution like so: &lt;/p&gt;

&lt;p&gt;&lt;a title="Docker enable WSL" href="https://geoligard.com/storage/posts/December2023/docker-enable-wsl.png"&gt;&lt;img title="Docker enable WSL integration" src="https://res.cloudinary.com/practicaldev/image/fetch/s--U-61RUks--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/docker-enable-wsl.png" alt="Docker enable WSL integration" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Resources for the WSL and Docker are managed through the &lt;code&gt;.wslconfig&lt;/code&gt; file. More about that &lt;a title="Install WSL2" href="https://learn.microsoft.com/en-us/windows/wsl/wsl-config" rel="noopener noreferrer"&gt;here&lt;/a&gt;. What you should do is create the &lt;code&gt;.wslconfig&lt;/code&gt; file in your text editor and add these values:&lt;/p&gt;



&lt;pre&gt;&lt;code&gt;[wsl2]
memory=4GB
processors=3
localhostForwarding=true&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;and paste the file inside the &lt;code&gt;C:\Users\&amp;lt;UserName&amp;gt;&lt;/code&gt; folder. You can adjust the memory and processor variables to fit your needs. For the changes from the file to be acknowledged you need to restart WSL by running &lt;code&gt;wsl --shutdown&lt;/code&gt; and running &lt;code&gt;wsl&lt;/code&gt; again.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Note: After these actions, Windows might act up and freeze all running programs. I am not yet sure why this is happening, but if that happens, restarting your PC should resolve the issue. Or you could just restart your PC right away, just in case, after you finish restarting WSL.&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;Install &lt;a title="Install Terminal" href="https://learn.microsoft.com/en-us/windows/terminal/install" rel="noopener noreferrer"&gt;Windows Terminal&lt;/a&gt; from the Microsoft Store, it will allow you to easily access Ubuntu's terminal inside WSL.&lt;/p&gt;



&lt;h2 id="installing-ssh"&gt;Installing SSH&lt;/h2&gt;



&lt;p&gt;The next step is to install SSH inside Ubuntu. Inside the Ubuntu terminal, run these commands to install the SSH server:&lt;/p&gt;



&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install openssh-server&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Check if SSH is running with:&lt;/p&gt;



&lt;pre&gt;&lt;code&gt;sudo systemctl status ssh&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Then run this command to allow connections to the SSH through the firewall:&lt;/p&gt;



&lt;pre&gt;&lt;code&gt;sudo ufw allow ssh&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Find your IP address with: &lt;/p&gt;



&lt;pre&gt;&lt;code&gt;ip a&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a title="Find IP address" href="https://geoligard.com/storage/posts/December2023/find-ip-address.png"&gt;&lt;img title="Find IP address" src="https://res.cloudinary.com/practicaldev/image/fetch/s--qFPwSaFs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/find-ip-address.png" alt="Find IP address" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the address, you will use your IP address in the next step.&lt;/p&gt;

&lt;p&gt;Make a folder that will hold your project in Ubuntu in &lt;code&gt;/home/&amp;lt;username&amp;gt;&lt;/code&gt; with:&lt;/p&gt;



&lt;pre&gt;&lt;code&gt;mkdir folder-name&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;That will be the destination folder in the next step to which the files will be copied from Windows's filesystem.&lt;/p&gt;



&lt;h2 id="connecting-phpstorm"&gt;Connecting everything through PhpStorm&lt;/h2&gt;



&lt;p&gt;We will now connect PhpStorm and our Windows project files with Ubuntu through SSH and basically copy any project files from Windows to Ubuntu any time a file is changed. Before we start, make sure that you have your IP address from the previous step and your Ubuntu username and password ready. Open PhpStorm to create a new deployment and SSH configuration. &lt;/p&gt;

&lt;p&gt;First, go to &lt;code&gt;File &amp;gt; Settings &amp;gt; Build, Execution, Deployment &amp;gt; Deployment&lt;/code&gt;. Click on the plus icon (+) to add a new server, name your server, and choose &lt;code&gt;SFTP&lt;/code&gt; as the connection type. Next click on the three dots (...) to add a new SSH configuration. In the newly opened modal, enter the Host, which is the IP address you saved earlier, set the auth type to &lt;code&gt;Password&lt;/code&gt;, and enter your Ubuntu username and password. Test the connection and save the settings. &lt;/p&gt;

&lt;p&gt;We are now back in the &lt;code&gt;Connection&lt;/code&gt; tab of the Deployment modal. The only thing left to add in that tab is &lt;code&gt;Root path&lt;/code&gt; and it should point to the location of your project inside WSL, for example: &lt;code&gt;/home/farscape/blog&lt;/code&gt;. Now switch to the &lt;code&gt;Mappings&lt;/code&gt; tab. The local path should point to the location of your project on Windows's filesystem, for example: &lt;code&gt;C:\blog&lt;/code&gt;. Deployment and Web path can point to the root folder of the Ubuntu project, like so &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Afterwards, go to &lt;code&gt;Deployment &amp;gt; Options&lt;/code&gt;. There, find the &lt;code&gt;Upload changed files automatically to the default server&lt;/code&gt; and set it to &lt;code&gt;Always&lt;/code&gt;. You can also tick the &lt;code&gt;Delete remote files when local are deleted&lt;/code&gt; checkbox.&lt;/p&gt;

&lt;p&gt;Since this setup includes quite a few steps, here are some screenshots of what everything might look like in the end:&lt;/p&gt;

&lt;p&gt;&lt;a title="Connection setup" href="https://geoligard.com/storage/posts/December2023/connection.png"&gt;&lt;img title="Connection setup" src="https://res.cloudinary.com/practicaldev/image/fetch/s--6NCX13v3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/connection.png" alt="Connection setup" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a title="Mappings setup" href="https://geoligard.com/storage/posts/December2023/mappings.png"&gt;&lt;img title="Mappings setup" src="https://res.cloudinary.com/practicaldev/image/fetch/s--J3qkBO5O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/mappings.png" alt="Mappings setup" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a title="SSH setup" href="https://geoligard.com/storage/posts/December2023/ssh-config.png"&gt;&lt;img title="SSH setup" src="https://res.cloudinary.com/practicaldev/image/fetch/s--e83tps_b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/ssh-config.png" alt="SSH setup" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a title="Deployment options" href="https://geoligard.com/storage/posts/December2023/deployment-options.png"&gt;&lt;img title="Deployment options" src="https://res.cloudinary.com/practicaldev/image/fetch/s--6RSMWpA5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/deployment-options.png" alt="Deployment options" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, right-click on the root folder of your project and upload all of the current project files to Ubuntu: &lt;/p&gt;

&lt;p&gt;&lt;a title="Upload to WSL" href="https://geoligard.com/storage/posts/December2023/upload-to-wsl.png"&gt;&lt;img title="Upload to WSL" src="https://res.cloudinary.com/practicaldev/image/fetch/s--eFSxP-LK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/upload-to-wsl.png" alt="Upload to WSL" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That might take some time the first time, but later on, the files will be automatically uploaded and synced. You can enable the &lt;code&gt;Remote host&lt;/code&gt; window in PhpStorm by going to &lt;code&gt;Tools &amp;gt; Deployment &amp;gt; Browse remote host&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the end, access your project's folder within the Ubuntu terminal and build the Docker project as you usually would with the &lt;code&gt;docker-compose build&lt;/code&gt; and &lt;code&gt;docker-compose up&lt;/code&gt; commands. Alternatively, if you are using Laravel Sail, just build and run your project with the corresponding commands. Wait until the project is built and the containers are up. That's it. You should be able to access your app at the URL you usually would and connect to your DB with the same credentials as before. You can also start and stop containers from the Docker Desktop app. Only this time around, everything should work smoothly, and your page load times should be drastically improved.&lt;/p&gt;

&lt;p&gt;By using this approach, we are getting the best of both worlds. This kind of setup allows us to use the advantages of Docker inside WSL without disrupting the usage of native Windows apps that are interacting with the codebase.&lt;/p&gt;



&lt;h2 id="caveats"&gt;Caveats&lt;/h2&gt;



&lt;p&gt;Now about those caveats I mentioned. There are some issues that you should be aware of. For example, PhpStorm provides a &lt;code&gt;one way connection&lt;/code&gt; that copies files automatically from your hard drive to the project located in WSL. So, if any files are changed inside WSL, they won't be automatically synced back to your main hard drive. Which in turn means that your version control tool won't pick up those new file changes.&lt;/p&gt;

&lt;p&gt;So if you were to access your PHP container inside Docker and type:&lt;/p&gt;



&lt;pre&gt;&lt;code&gt;php artisan make:model Test&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;That file would be created inside WSL only. Luckily, there is a solution, but it includes a bit of manual labor. If you right-click on any folder located on WSL inside PhpStorm, you can sync the files from WSL back to the main filesystem. You can also use options like &lt;code&gt;Upload here&lt;/code&gt; or &lt;code&gt;Download from here&lt;/code&gt; to upload or download files from entire folders on both sides of the connection.&lt;/p&gt;

&lt;p&gt;&lt;a title="Sync with local" href="https://geoligard.com/storage/posts/December2023/sync.png"&gt;&lt;img title="Sync with local" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ArLC35-Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/sync.png" alt="Sync with local" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From that same drop-down menu, you can also exclude folders you might not want synced.&lt;/p&gt;

&lt;p&gt;When it comes to caveats, that's actually the main problem: from time to time, you will have to manually sync some of the files back and forth. &lt;/p&gt;



&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;



&lt;p&gt;While this whole approach may seem a bit tedious in the beginning, once you familiarize yourself with the setup, make the necessary adjustments the first time, and experiment a bit, later on you will be able to configure each new project more quickly and with ease. Most importantly, you'll be able to enjoy the performance boost without having to move your native apps to WSL.&lt;/p&gt;

&lt;p&gt;This method isn't ideal; you might think it's too troublesome to sync the files from time to time manually, it might be an overkill for your particular use case, and that's perfectly fine. On the other hand, it might be just right for you and your projects. For now, I accepted the good with the bad, its shortcomings don't actually bother me that much anymore, and I really enjoy the better page load times and being able to use my tools the same as before, without any issues.&lt;/p&gt;



&lt;h2 id="bonus-step"&gt;Bonus step&lt;/h2&gt;



&lt;p&gt;Now that your setup is up and running, you might want to check out how you can interact with Docker containers without ever having to leave PhpStorm and how we can configure a PHP CLI interpreter using the Docker compose file: &lt;a title="Interact with Docker containers without leaving PhpStorm" href="https://dev.to/geoligard/interact-with-docker-containers-without-leaving-phpstorm-1o20"&gt;Interact with Docker containers without leaving PhpStorm&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enjoyed the article? Consider subscribing to my &lt;a href="https://geoligard.com/newsletter"&gt;newsletter&lt;/a&gt; for future updates.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geoligard.com/docker-on-wsl-with-phpstorm-best-of-both-worlds"&gt;geoligard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>windows</category>
      <category>wsl</category>
      <category>phpstorm</category>
    </item>
    <item>
      <title>Using Laravel to interact with OpenAI's Assistants API (with Vision)</title>
      <dc:creator>Goran Popović</dc:creator>
      <pubDate>Tue, 13 Feb 2024 23:45:07 +0000</pubDate>
      <link>https://forem.com/geoligard/using-laravel-to-interact-with-openais-assistants-api-with-vision-3nke</link>
      <guid>https://forem.com/geoligard/using-laravel-to-interact-with-openais-assistants-api-with-vision-3nke</guid>
      <description>&lt;p&gt;A couple of months ago Open AI released their Assistant API which is at the time of writing this article still in beta. In short, Assistants are a new and (hopefully) better way of utilizing OpenAI models. Before, when using the Chat Completions API, you had to keep a history of each message that was exchanged between the user and model, and then send that whole history each time back to the API, because Chat Completions API isn't stateful.&lt;/p&gt;

&lt;p&gt;Enter Assistants. Assistants are stateful and will keep a history for you, so you can easily send just the last user's message, and receive back an answer. Besides Function Calling, they also include cool tools like Code Interpreter and Knowledge Retrieval.&lt;/p&gt;



&lt;h2&gt;Getting Started&lt;/h2&gt;



&lt;p&gt;Now, to interact with OpenAI API through Laravel we are going to use an already popular package developed by Nuno Maduro and Sandro Gehri: &lt;a title="OpenAI PHP client" href="https://github.com/openai-php/client" rel="noopener"&gt;&lt;/a&gt;&lt;a href="https://github.com/openai-php/client"&gt;https://github.com/openai-php/client&lt;/a&gt; and a Laravel specific wrapper of that package: &lt;a title="OpenAI Laravel client" href="https://github.com/openai-php/laravel" rel="noopener"&gt;&lt;/a&gt;&lt;a href="https://github.com/openai-php/laravel"&gt;https://github.com/openai-php/laravel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, simply require the Laravel wrapper package:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require openai-php/laravel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;That will install the required dependencies as well, including the OpenAI PHP Client package. After that execute the install command:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan openai:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Next, you should add your OpenAI API key inside your &lt;code&gt;.env&lt;/code&gt; file:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-...
&lt;span class="nv"&gt;OPENAI_ORGANIZATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;org-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Find or create your API key in the API keys section:&lt;/p&gt;

&lt;p&gt;&lt;a title="Api keys" href="https://geoligard.com/storage/posts/December2023/api-keys.png"&gt;&lt;img title="api keys" src="https://res.cloudinary.com/practicaldev/image/fetch/s--4PgOAq8O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/api-keys.png" alt="api keys" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we are ready to use the package to interact with the API. But, before that, let's create an Assistant. Bare in mind that Assistant can also be created through API, but for simplicity in speed we are going to create an assistant in OpenAI's Playgound. Head to: &lt;a title="OpenAI playground" href="https://platform.openai.com/playground" rel="noopener"&gt;&lt;/a&gt;&lt;a href="https://platform.openai.com/playground"&gt;https://platform.openai.com/playground&lt;/a&gt; and create an Assistant. Give him any name you like, enter any instructions you may have, select a model and you may or may not enable additional tools. When you do save your changes, make sure to copy the assistant's ID, we are going to need it later.&lt;/p&gt;

&lt;p&gt;&lt;a title="Create Assistant" href="https://geoligard.com/storage/posts/December2023/create-assistant.png"&gt;&lt;img title="Create Assistant" src="https://res.cloudinary.com/practicaldev/image/fetch/s--N6IbceiV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/create-assistant.png" alt="Create Assistant" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, now, imagine we are developing an app where we are in charge of the backend. User will enter a chat message on the frontend, typically some kind of a question for our Assistant to answer, frontend will then use our app's internal API, and send us the message the user entered. On the backend side, we are going to make a request to the Assistant and await his response. When we get the response, we are going to return it to the frontend to display it to the user. This article will just focus on the backend side of the process. I mentioned that in theory some frontend would be calling our backend API, but we could use Postman just as well to simulate that process, which we will actually do later.&lt;/p&gt;

&lt;p&gt;Let's create just one route and an &lt;code&gt;AssistantController.php&lt;/code&gt; in which we will perform our logic.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:controller AssistantController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;







&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// api.php&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/assistant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AssistantController&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="s1"&gt;'generateAssistantsResponse'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;







&lt;h2&gt;Creating and running threads&lt;/h2&gt;



&lt;p&gt;Before we create our main &lt;code&gt;generateAssistantsResponse&lt;/code&gt; function, let's create a few helper functions. The way the Assistant's flow works, we would have to create a &lt;code&gt;thread&lt;/code&gt; using our Assistant's ID, which is a type of holder for any messages exchanged between a user and an assistant, then we need to submit an actual user's &lt;code&gt;message&lt;/code&gt;, and trigger a &lt;code&gt;run&lt;/code&gt;. When the run finishes successfuly we should get our response back.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AssistantController.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;OpenAI\Laravel\Facades\OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AssistantController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;submitMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assistantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;threads&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;messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$userMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;threads&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;runs&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="n"&gt;threadId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'assistant_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$assistantId&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;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$run&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;createThreadAndRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assistantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;threads&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="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;submitMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assistantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$thread&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;$userMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nv"&gt;$thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$run&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;waitOnRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$threadId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"queued"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"in_progress"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;threads&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;runs&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;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;threadId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;runId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$run&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="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$run&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;getMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'asc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$messageId&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;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'order'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'limit'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messageId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'after'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$messageId&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="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;threads&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;messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;In short, we will use the &lt;code&gt;createThreadAndRun&lt;/code&gt; function to create a thread, submit a message to the thread, and create a run. There is an equivalent &lt;code&gt;createAndRun&lt;/code&gt; function in the package we are using, but this function we created might help you initially to better understand what is going on. We will then use &lt;code&gt;waitOnRun&lt;/code&gt; function to wait for the run to complete and finally we can use &lt;code&gt;getMessages&lt;/code&gt; function to get the response from the assistant.&lt;/p&gt;



&lt;h2&gt;Merging the pieces together&lt;/h2&gt;



&lt;p&gt;Now, we can create our main function &lt;code&gt;generateAssistantsResponse&lt;/code&gt; as defined in the &lt;code&gt;assistant&lt;/code&gt; route we created previously.&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;generateAssistantsResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// hard coded assistant id&lt;/span&gt;
    &lt;span class="nv"&gt;$assistantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'asst_someId'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nv"&gt;$userMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// create new thread and run it&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createThreadAndRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assistantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

    &lt;span class="c1"&gt;// wait for the run to finish&lt;/span&gt;
    &lt;span class="nv"&gt;$run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;waitOnRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$thread&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// get the latest messages after user's message&lt;/span&gt;
        &lt;span class="nv"&gt;$messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'asc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$message&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;$messagesData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$messages&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messagesData&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$messagesCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messagesData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$assistantResponseMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// check if assistant sent more than 1 message&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messagesCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messagesData&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// concatenate multiple messages&lt;/span&gt;
                    &lt;span class="nv"&gt;$assistantResponseMessage&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\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="c1"&gt;// remove the last new line&lt;/span&gt;
                &lt;span class="nv"&gt;$assistantResponseMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rtrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assistantResponseMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// take the first message&lt;/span&gt;
                &lt;span class="nv"&gt;$assistantResponseMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$messagesData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s2"&gt;"assistant_response"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$assistantResponseMessage&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;\Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Something went wrong; assistant didn\'t respond'&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;\Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Something went wrong; assistant run wasn\'t completed successfully'&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;So, we take the user's &lt;code&gt;message&lt;/code&gt; from the &lt;code&gt;request&lt;/code&gt;, and use that data along with our assistant's ID to generate a new thread, submit a message, and start the run. From the &lt;code&gt;createThreadAndRun&lt;/code&gt; function we are getting all of the necessary data back to us which we can use further in the process. After the run has started we will periodically check if the run has been completed.&lt;/p&gt;

&lt;p&gt;After a few seconds the &lt;code&gt;status&lt;/code&gt; of the run will be set to &lt;code&gt;completed&lt;/code&gt;. As soon as that happens, we are getting the latest messages after the one the user sent. Since (per OpenAI docs) assistant can sometimes send more than one message (never happened to me, but just in case) we are checking the number of messages sent and concatenating them into a single response for simplicity. Otherwise, if there is just one message, we just get the first message. In the end the assistant's message is returned in a JSON response.&lt;/p&gt;



&lt;h2&gt;Using assistant functions to send images to the Vision API&lt;/h2&gt;



&lt;p&gt;Note: before we start using the Vision API, please bare in mind that while you can use the Chat and Assistants API with GPT-3 models, you will need access to GPT-4 models to use Vision, and you will need to spend $1 in order to unlock access to GPT-4 models: &lt;a title="Access to GPT-4" href="https://help.openai.com/en/articles/7102672-how-can-i-access-gpt-4" rel="noopener"&gt;&lt;/a&gt;&lt;a href="https://help.openai.com/en/articles/7102672-how-can-i-access-gpt-4"&gt;https://help.openai.com/en/articles/7102672-how-can-i-access-gpt-4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ok, we are ready to talk about &lt;code&gt;assistant functions&lt;/code&gt; and how we can utilize them to pass different data (3rd party or from our own app) to the assistant. With Vision API you can for example allow your user to upload an image, and then the &lt;code&gt;GPT-4&lt;/code&gt; model will be able to take in that image and answer different questions about it. Now, the thing is that Vision isn't yet available for Assistants API. If you try to upload an image and send it to the assistant it won't be able to describe the contents of that image. But there is a workaround.&lt;/p&gt;

&lt;p&gt;Enter function calling. Using assistant functions, we are able to tell the assistant to call a specific function which will in turn call another API - Chat Completions API: &lt;a title="Chat API" href="https://platform.openai.com/docs/api-reference/chat/create" rel="noopener"&gt;&lt;/a&gt;&lt;a href="https://platform.openai.com/docs/api-reference/chat/create"&gt;https://platform.openai.com/docs/api-reference/chat/create&lt;/a&gt; which does have Vision available. Later on we will feed that data from the Chat API to our assistant so he can respond accordingly.&lt;/p&gt;

&lt;p&gt;Back in our &lt;code&gt;AssistantController.php&lt;/code&gt;, let's start with another helper function. We will create a &lt;code&gt;processRunFunctions&lt;/code&gt; method that checks if our assistant &lt;code&gt;run&lt;/code&gt; requires any action to be performed, in this case if any function should be called. If the assistant decides that the &lt;code&gt;describe_image&lt;/code&gt; function should be called we will fire up the Chat API and ask it to provide us with the image description based on the message and the image user has already provided. Then we will submit the Vision related data back to the assistant and wait for that run to complete. Bare in mind that we are increasing the default value for &lt;code&gt;max_tokens&lt;/code&gt; so the response is not cut off.&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;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;processRunFunctions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// check if the run requires any action&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'requires_action'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;requiredAction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'submit_tool_outputs'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Extract tool calls&lt;/span&gt;
        &lt;span class="c1"&gt;// multiple calls possible&lt;/span&gt;
        &lt;span class="nv"&gt;$toolCalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;requiredAction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;submitToolOutputs&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="nv"&gt;$toolOutputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$toolCalls&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$toolCall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$toolCall&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;$arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$toolCall&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'describe_image'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$visionResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                    &lt;span class="s1"&gt;'model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'gpt-4-vision-preview'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'messages'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                &lt;span class="p"&gt;[&lt;/span&gt;
                                    &lt;span class="s2"&gt;"type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="s2"&gt;"text"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$arguments&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;
                                &lt;span class="p"&gt;],&lt;/span&gt;
                                &lt;span class="p"&gt;[&lt;/span&gt;
                                    &lt;span class="s2"&gt;"type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="s2"&gt;"image_url"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"url"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$arguments&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="p"&gt;],&lt;/span&gt;
                                &lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="p"&gt;]&lt;/span&gt;
                        &lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="s1"&gt;'max_tokens'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;
                &lt;span class="p"&gt;]);&lt;/span&gt;

                &lt;span class="c1"&gt;// you get 1 choice by default&lt;/span&gt;
                &lt;span class="nv"&gt;$toolOutputs&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="s1"&gt;'tool_call_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$toolCall&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="s1"&gt;'output'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$visionResponse&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
                &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;threads&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;runs&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;submitToolOutputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;threadId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;runId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$run&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="n"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'tool_outputs'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$toolOutputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;waitOnRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;threadId&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;$run&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;We can now amend our main method &lt;code&gt;generateAssistantsResponse&lt;/code&gt; to process the image user uploaded, and process any functions the assistant may call. I won't get into detail on how to validate and save the image user provided, just bare in mind that the image can be sent as an URL or base64 to the Vision, but an URL is preferred.&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;generateAssistantsResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$assistantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'asst_someId'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$userMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// check if user uploaded a file&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// validate image&lt;/span&gt;

        &lt;span class="c1"&gt;// store the image and get the url - url hard coded for this example&lt;/span&gt;
        &lt;span class="nv"&gt;$imageURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://images.unsplash.com/photo-1575936123452-b67c3203c357'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// concatenate the URL to the message to simplify things&lt;/span&gt;
        &lt;span class="nv"&gt;$userMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$userMessage&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$imageURL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createThreadAndRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assistantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;waitOnRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$thread&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;// check if any assistant functions should be processed&lt;/span&gt;
    &lt;span class="nv"&gt;$run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;processRunFunctions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$run&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;threadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'asc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$message&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;The last thing to do is to add a function definition. You can add a function definition through the API, but again, since it is more straightforward for this example we will add the function definition through Playground. Here is the definition we will use:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"describe_image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Describe an image user uploaded, understand the art style, and use it as an inspiration for constructing an appropriate prompt that will be later used to generate a skybox."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"user_message"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Message the user sent along with the image, without the URL itself"&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;"image"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"URL of the image user provided"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&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="s2"&gt;"user_message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;And here is how that might look like in the Playground.&lt;/p&gt;

&lt;p&gt;&lt;a title="Add function" href="https://geoligard.com/storage/posts/December2023/add-function.png"&gt;&lt;img title="Add function" src="https://res.cloudinary.com/practicaldev/image/fetch/s--2T9ECOZ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/add-function.png" alt="Add function" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a title="Describe image function" href="https://geoligard.com/storage/posts/December2023/describe-image-function.png"&gt;&lt;img title="Describe image function" src="https://res.cloudinary.com/practicaldev/image/fetch/s--a5xi3GJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/describe-image-function.png" alt="Describe image function" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is how the whole API request/response flow might look like in Postman.&lt;/p&gt;

&lt;p&gt;&lt;a title="Postman example" href="https://geoligard.com/storage/posts/December2023/postman-example.png"&gt;&lt;img title="Postman example" src="https://res.cloudinary.com/practicaldev/image/fetch/s--cUjpCiN7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://geoligard.com/storage/posts/December2023/postman-example.png" alt="Postman example" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In case you want to copy the whole code from &lt;code&gt;AssistantController.php&lt;/code&gt; head out to &lt;a title="Gist example" href="https://gist.github.com/goran-popovic/b104e2cc3c16db9312cd1e90d4e5e256" rel="noopener"&gt;Gist&lt;/a&gt;.&lt;/p&gt;



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



&lt;p&gt;In conclusion, bare in mind that this is just a basic example to showcase some of the possibilities, and that the code can and should be improved in many ways. For example, you probably shouldn't hard code your Assistant ID, instead you could keep it in your database model. Currently the only way to check if a run has been completed is to periodically check if that has happened, which is far from ideal, but in the documentation is it mentioned that will change in the future: &lt;a title="OpenAI Assistants API limitations" href="https://platform.openai.com/docs/assistants/how-it-works/limitations" rel="noopener"&gt;&lt;/a&gt;&lt;a href="https://platform.openai.com/docs/assistants/how-it-works/limitations"&gt;https://platform.openai.com/docs/assistants/how-it-works/limitations&lt;/a&gt;. Instead of waiting for the runs to complete in a single request you could dispatch parts of code to a Laravel Job and run it asynchronously. After a Job is completed it could broadcast an event through Websockets and send a notification to the frontend with the response from the assistant, or you could use Webhooks to send results to other users.&lt;/p&gt;

&lt;p&gt;Also, as I already mentioned the Assistants API is in beta and it will probably change and evolve in the coming weeks or months.&lt;/p&gt;

&lt;p&gt;That's it for this article, hope it provided you with a good starting point for using the Assistants API.&lt;/p&gt;

&lt;p&gt;Happy building!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enjoyed the article? Consider subscribing to my &lt;a href="https://geoligard.com/newsletter"&gt;newsletter&lt;/a&gt; for future updates.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geoligard.com/using-laravel-to-interact-with-openai-s-assistants-api-with-vision"&gt;geoligard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>ai</category>
      <category>openai</category>
      <category>api</category>
    </item>
  </channel>
</rss>
