PHP

★ A package to watch for file system changes in PHP

[AdSense-A]

Our team has released a new package called file-system-watcher. As the name implies, this package can watch changes in the file system and let you act on those changes.

Using the package

Here's a simple example that allows you to run some code when a new file gets added.

use SpatieWatcherWatch;

Watch::path($directory) ->onFileCreated(function (string $newFilePath) { // do something... }) ->start();

After calling start, the watcher will start a never-ending loop. When a file is created in the given $directory, the closure passed in onFileCreated will be executed.

There are methods for each kind of file change onFileCreated(), onFileUpdated(), onFileDeleted(), onDirectoryCreated(), onDirectoryDeleted().

Pretty simple, right?

How it works under the hood

Under the hood, this package leverages (https://github.com/paulmillr/chokidar), a popular JS library that can efficiently handle file system changes without requiring much memory or CPU.

To see a demo of the package and learn how we integrated our PHP code with a JS package, watch this fragment of a stream in which I introduced our file-system-watcher.

In closing

There are a few more options available in file-system-watcher. Head over to the readme on GitHub to learn more.

Be sure also to check out the packages our team has created previously. I'm sure there is something that you could use in your next project.

(more…)

By , ago
PHP

★ Dealing with expired signed URLs in Laravel

[AdSense-A]

Out of the box, Laravel comes with the ability to generate "signed" URLs. These URLs have a hash in their query string that verifies that the URL was not modified.

At Flare, we use these signed URLs to add action links in mail notifications. The action links allow users to snooze and resolve errors right from the mail without having to be logged in. Pretty convenient!

My buddy Dries Vints noticed a slight drawback. He got a mail from Flare that contains these action links. A few hours after the mail arrived, he clicked one of the action links. This is what he saw.

screenshot

This error screen is confusing: you might think that the links in the mail are invalid. To keep things secure, we use a short lifetime for our signed URLs. Dries got this screen because the link had expired.

We can improve on this by creating a dedicated error message when clicking expired or invalid links. Luckily, this is not that difficult.

When you try to validate a signed URL and the validation fails, Laravel will throw a dedicated exception IlluminateRoutingExceptionsInvalidSignatureException In your exception handler, you can listen for that exception and render a dedicated view.

// in app/Exceptions/Handler.php

use Illuminate<span class="hljs-title">Routing<span class="hljs-title">Exceptions<span class="hljs-title">InvalidSignatureException;

public function render($request, Throwable $exception) { if ($exception instanceof InvalidSignatureException) { return response()->view('errors.link-expired')->setStatusCode(Response::HTTP_FORBIDDEN); }

<span class="hljs-comment">// ...</span>

}

With that code in place, this is what Dries will see when clicking another expired link in the future.

screenshot

And that is all there is to it. To avoid confusions for your users, I highly recommend setting up a dedicated error message when using signed URLs.

Thanks for bringing this to my attention, Dries.

(more…)

By , ago
PHP

★ Develop faster by adding dev routes file in a Laravel app

[AdSense-A]

Laravel's awesome closure based routing was probably one of the first features I fell in love with. I take it for granted now, but back in the days, such a simple way of adding a route felt like a glass of water in hell compared to the other frameworks.

Typically, you would only add routes that are necessary for the users of your app. Something that I have been doing for a long time is to create a routes file, called dev.php, with routes that can help with app development.

Here's some of the content of routes/dev.php from the Flare codebase.

Route::prefix('mails')->group(function (): void {
    Route::get('Invoice', function () {
        $invoice = Team::first()->findInvoice('in_1FEYTqF2myXP8kzfgFqu4ofw');
    <span class="hljs-keyword">return</span> View::make(<span class="hljs-string">'cashier::receipt'</span>, array_merge([
        <span class="hljs-string">'vendor'</span> =&gt; <span class="hljs-string">'Flare'</span>,
        <span class="hljs-string">'localInvoice'</span> =&gt; Invoice::first(),
        <span class="hljs-string">'product'</span> =&gt; <span class="hljs-string">'Pro Subscription'</span>,
    ], [
        <span class="hljs-string">'invoice'</span> =&gt; $invoice,
        <span class="hljs-string">'owner'</span> =&gt; $invoice-&gt;owner,
        <span class="hljs-string">'user'</span> =&gt; $invoice-&gt;owner,
    ]));
});

Route::get(<span class="hljs-string">'Invoice'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
    $invoice = Team::first()-&gt;findInvoice(<span class="hljs-string">'in_1FEYTqF2myXP8kzfgFqu4ofw'</span>);

    <span class="hljs-keyword">return</span> View::make(<span class="hljs-string">'cashier::receipt'</span>, array_merge([
        <span class="hljs-string">'vendor'</span> =&gt; <span class="hljs-string">'Flare'</span>,
        <span class="hljs-string">'localInvoice'</span> =&gt; Invoice::first(),
        <span class="hljs-string">'product'</span> =&gt; <span class="hljs-string">'Pro Subscription'</span>,
    ], [
        <span class="hljs-string">'invoice'</span> =&gt; $invoice,
        <span class="hljs-string">'owner'</span> =&gt; $invoice-&gt;owner,
        <span class="hljs-string">'user'</span> =&gt; $invoice-&gt;owner,
    ]));
});

Using these routes, we can quickly render a mail in the browser and work on its content and layout. Of course, you can add routes for other things as well. Here's another example that we use to quickly render views that users would see when they click a signed URL (for example, in Flare's mail notifications).

Route::prefix('signed')->group(function (): void {
Route::get('snooze', function () {
/** @var Error $error */
$errorOccurrence = ErrorOccurrence::factory()->create();
    <span class="hljs-keyword">return</span> redirect($errorOccurrence-&gt;error-&gt;signedSnoozeUrl(<span class="hljs-string">'[email protected]'</span>));
});

});

Of course, you only don't want to have these routes in production, but only in the local environment. It's easy to set this up in the route service provider.


namespace App<span class="hljs-title">Providers;

// other imports...

class RouteServiceProvider extends ServiceProvider { // ...

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">map</span><span class="hljs-params">(Router $router)</span>
</span>{
	    <span class="hljs-comment">// mapping of other route files</span>
	    
	    <span class="hljs-keyword">if</span> (app()-&gt;environment(<span class="hljs-string">'local'</span>)) {
        	<span class="hljs-keyword">$this</span>-&gt;mapDevRoutes($router);
		}  
    }
}

<span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mapDevRoutes</span><span class="hljs-params">(Router $router)</span>
</span>{
    $router
        -&gt;middleware(<span class="hljs-string">'web'</span>)
        -&gt;prefix(<span class="hljs-string">'dev'</span>)
        -&gt;group(base_path(<span class="hljs-string">'routes/dev.php'</span>));
}

}

As you can see in the code above, I typically use a prefix dev to avoid conflicts with other routes in the app. Of course, you can name the routes file and prefix to whatever you like.

I really like using dev only routes, and hope that using a dev.php routes file can help you as well.

(more…)

By , ago
PHP

PHP's bus factor

PHP is a fantastic language that has seen many cool improvements over the past few years. What you may not be aware of is that only a very few people can work on the language Read more…

By , ago