Logbook: 2021 February

Updated on

Install PHP 8 on Windows

Today I managed to install the latest PHP version 8 on my Windows machine. It’s a pretty straightforward process:

List Pages with Specific Tag on Hugo

We can combine the range and the where functions on Hugo to get a list of pages with some specific tag. Let say we want to get the first 5 recent pages that contain the logbook tag, we can achieve it like this:

    {{- range first 5 (where .Site.RegularPages "Params.tags" "intersect" (slice "logbook")) -}}
        <a href="{{ .RelPermalink }}">{{ .Title }}</a>
    {{- end -}}

The intersect operator accepts an array, that’s why we need to turn the logbook into an array first.

Postmark Custom Metadata on Laravel

Postmarks support custom metadata to be sent along with the outbound message. You can do so by adding a custom HTTP header named X-PM-Metadata-SOMETHING. On Laravel mailable, you can set this header by accessing the underlying Swift_Message instance through the withSwiftMessage function. I create a PostmarkMailable class that extends the Laravel Mailable class.


namespace App\Mail;

use Swift_Message;
use Illuminate\Mail\Mailable as BaseMailable;

abstract class PostmarkMailable extends BaseMailable
    // Omitted...

    public function metadata($key, $value = null)
        if (is_array($key)) {
            foreach ($key as $k => $v) {
                $this->metadata($k, $v);

            return $this;

        $this->withSwiftMessage(function (Swift_Message $message) use ($key, $value) {
            $message->getHeaders()->addTextHeader("X-PM-Metadata-{$key}", $value);

        return $this;

Now, when we send a mailable that extends this PostmarkMailable class, we can also the metadata.

// Some mailable that extends the PostmarkMailable
$mailable = new \App\Mail\OrderShipped();

// Set a single metadata
$mailable->metadata('order-id', 12345);

// Or multiple metadata at once
    'order-id' => 12345,
    'customer-name' => 'Luke Skywalker',

Currently, you can only set up to 10 metadata. The field name is also limited to 20 characters, while the value is restricted to 80 characters.

Automatically Resize the Image on Markdown Post on Hugo

I already implemented this feature back when I switch to Hugo months ago. However, back then I used shortcode instead of a built-in markdown image render hook. I tried the image render hook, but it threw an error. I never bother to found out why and stick with the shortcode.

Turns out, the error happened because I tried to process an image from the static directory within the image render hook. We can’t get access to the image file within the static directory from a render hook. If you want to process an image from the static directory, use shortcode.

Now I have updated my layouts/_default/_markup/render-image.html to automatically resize any image larger than 1080px in width:

    <a href="{{ .Destination | safeURL }}">
        {{- $img := .Page.Resources.GetMatch .Destination -}}
        {{- if gt $img.Width 1080 -}}
            {{- $img = $img.Resize "1080x" -}}
        {{- end -}}
        <img src="{{ $img.Permalink | safeURL }}" {{- with .Text | plainify }} alt="{{ . }}"{{ end -}} {{- with .Title }} title="{{ . }}"{{ end -}}>
    {{- with .Text | markdownify }}<figcaption>{{ . }}</figcaption>{{ end -}}

Host File on Windows 10

On Windows 10, the host file is located at:


The format is the same as the one found on Linux or macOS. So unfortunately you cannot set a wildcard subdomain entry too.   foo.example.com      # Some comment      project.test      secret.project.test

Laravel Subdomain Routing

I know that Laravel has support for subdomain routing for a long time. It goes as early as version 4 in 2013—the first version of Laravel that I used. But I never really tried that feature, until today. It just works out-of-the-box. Of course, you still need to properly configure your webserver to point those subdomains to your Laravel’s public directory. But it still blew my mind.

Route::domain('{user}.example.com')->group(function () {
    Route::get('/messages/{id}', function ($user, $id) {
        // Get the {user} value through $user