Matthew Hodge
Full Stack Developer

PHP: Yield vs. Traditional Loop

When working with large datasets, such as calculating prime numbers, efficiency is key. In PHP, the yield keyword offers a powerful way to optimize both performance and memory usage. Let’s explore how it compares to traditional loops.

The Problem: Calculating Prime Numbers

Prime numbers are fundamental in mathematics and computing. However, generating a large list of primes can be resource-intensive. Let’s compare two approaches in PHP:

1. Traditional Loop (Without yield)

function getPrimesWithoutYield($limit) {
    $primes = [];
    for ($i = 2; $i < $limit; $i++) {
        $isPrime = true;
        for ($j = 2; $j <= sqrt($i); $j++) {
            if ($i % $j == 0) {
                $isPrime = false;
                break;
            }
        }
        if ($isPrime) {
            $primes[] = $i;
        }
    }
    return $primes;
}

2. Using yield

function getPrimesWithYield($limit) {
    for ($i = 2; $i < $limit; $i++) {
        $isPrime = true;
        for ($j = 2; $j <= sqrt($i); $j++) {
            if ($i % $j == 0) {
                $isPrime = false;
                break;
            }
        }
        if ($isPrime) {
            yield $i;
        }
    }
}

Performance Comparison

Testing both methods to calculate primes up to 10,000,000. Here are the results:

  • Without yield:
    Time: ~25 seconds, Memory: ~42 MB
  • With yield:
    Time: ~23 seconds, Memory: ~0 MB

Why Use yield?

  1. Memory Efficiency: yield generates values on-the-fly, avoiding the need to store the entire list in memory.
  2. Lazy Evaluation: Values are computed only when needed, reducing unnecessary computation.
  3. Scalability: Ideal for processing large datasets without running into memory limits.

Conclusion

For tasks like calculating primes, yield is a game-changer. It offers significant memory savings and maintains comparable speed, making it the better choice for large-scale computations.


Let's build a simple dashboard shell using Tailwind CSS's command-line approach. This method is perfect for quick prototyping and smaller projects where you don't need a full build system.

Step 1: Set Up Your Project

First, create a new project folder and initialize it:

mkdir tailwind-dashboard
cd tailwind-dashboard
npm init -y

Step 2: Install Tailwind CSS

Install Tailwind CSS as a development dependency:

npm install tailwindcss @tailwindcss/cli

Step 3: Create CSS Input File

Create a source CSS file that includes Tailwind:

// filepath: input.css
@import "tailwindcss";

Step 4: Create HTML File

Create an index.html file with our dashboard structure:

// filepath: index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Tailwind Dashboard</title>
  <link rel="stylesheet" href="./output.css">
</head>

<body class="bg-gray-100">
  <div class="min-h-screen flex flex-col">
    <header class="bg-header shadow-md">
      <nav class="bg-gray-800">
        <div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
          <div class="relative flex h-16 items-center justify-between">

            <div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
              <div class="flex shrink-0 items-center">
                <img class="h-8 w-auto" src="https://picsum.photos/100" alt="Your Company">
              </div>
              <div class="hidden sm:ml-6 sm:block">
                <div class="flex space-x-4">
                  <a href="#" class="rounded-md bg-gray-900 px-3 py-2 text-sm font-medium text-white"
                    aria-current="page">FIRST</a>
                  <a href="#"
                    class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">SECOND</a>
                  <a href="#"
                    class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">THIRD</a>
                  <a href="#"
                    class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">FOURTH</a>
                </div>
              </div>
            </div>
            <div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
              <button type="button"
                class="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden">
                <svg class="size-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon">
                  <path stroke-linecap="round" stroke-linejoin="round"
                    d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" />
                </svg>
              </button>


              <div class="relative ml-3">
                <div>
                  <button type="button"
                    class="relative flex rounded-full bg-gray-800 text-sm focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden"
                    id="user-menu-button" aria-expanded="false" aria-haspopup="true">
                    <img class="size-8 rounded-full" src="https://picsum.photos/256/256" alt="">
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="sm:hidden" id="mobile-menu">
          <div class="space-y-1 px-2 pt-2 pb-3">
            <a href="#" class="block rounded-md bg-gray-900 px-3 py-2 text-base font-medium text-white"
              aria-current="page">FIRST</a>
            <a href="#"
              class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white">SECOND</a>
            <a href="#"
              class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white">THIRD</a>
            <a href="#"
              class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white">FOURTH</a>
          </div>
        </div>
      </nav>
    </header>

    <div class="flex flex-1 p-4">
      <aside id="sidebar" class="bg-sidebar w-64 fixed md:static">
        SIDEBAR
      </aside>

      <main class="flex-1">
        MAIN
      </main>
    </div>

    <footer class="bg-white py-4 px-6 shadow-inner">
      FOOTER
    </footer>
  </div>
</body>

</html>

Step 5: Build CSS

Now, let's use the Tailwind CLI to build our CSS file:

npx @tailwindcss/cli -i input.css -o output.css --watch

This command:

Takes our input.css with the Tailwind directives Processes it and outputs to output.css The --watch flag keeps the process running and rebuilds when files change

Step 6: View Your Dashboard

Open index.html in your browser to see your dashboard. As you modify your HTML, the CSS will automatically update thanks to the --watch flag.

Extending the Dashboard

Note that you have the basic shell, enhance it for your use case

Production Build

When you're ready for production, generate a minified CSS file:

npx tailwindcss -i input.css -o output.css --minify

This creates a smaller CSS file optimized for production.

Conclusion

You've successfully created a simple dashboard shell using Tailwind CSS's CLI approach. This method is perfect for prototyping and smaller projects where you don't need a complex build system.

The Tailwind CLI provides a streamlined workflow that makes it easy to create beautiful, responsive interfaces without the complexity of larger build systems.

For more information, check out the Tailwind CSS documentation.


Creating your own blog with modern web technologies has never been easier. In this tutorial, we'll walk through setting up a blog using Nuxt 3, Nuxt Content module for markdown support, and Tailwind CSS for styling. This powerful combination gives you a fast, SEO-friendly blog with an excellent developer experience.

GitHub Repo

Prerequisites

Before starting, make sure you have:

  • Node.js (version 18 or newer)
  • npm or yarn
  • Basic knowledge of Vue.js

Step 1: Create a New Nuxt Project

First, let's create a new Nuxt project:

npx nuxi@latest init your-blog
cd your-blog

This command creates a new Nuxt 3 project in the your-blog directory. After the initialization completes, navigate into the project folder.

Step 2: Install Dependencies

Next, we need to install the Nuxt Content module for managing markdown content and Tailwind CSS:

npm install @nuxt/content
npm install tailwindcss @tailwindcss/vite

Step 3: Configure Nuxt

Update your nuxt.config.ts file to include the necessary modules and configurations:

// filepath: nuxt.config.ts
import tailwindcss from "@tailwindcss/vite";

export default defineNuxtConfig({
  compatibilityDate: "2024-11-01",
  devtools: { enabled: true },
  modules: ['@nuxt/content'],  
  vite: {
    plugins: [
      tailwindcss(),
    ],
  },
});

Step 4: Configure Content Module

Create a content.config.ts file in the root of your project:

// filepath: content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'

export default defineContentConfig({
  collections: {
    content: defineCollection({
      type: 'page',
      source: '**/*.md'
    }),    
    blog: defineCollection({
      source: 'blog/*.md',
      type: 'page',
    })
  }
})

This configures the Nuxt Content module to recognize all markdown files as content pages.

Step 5: Create App Layout

First, let's create the main app layout. Create an app.vue file in the root directory:

// filepath: app.vue
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

This simple template uses Nuxt's layout system and renders the current page.

Step 6: Create Home Page

Create an index.vue file in the pages directory:

// filepath: pages/index.vue
<script setup lang="ts">
const { data: home } = await useAsyncData(() => queryCollection('content').path('/').first())

useSeoMeta({
  title: home.value?.title,
  description: home.value?.description
})
</script>

<template>
  <ContentRenderer v-if="home" :value="home" />
  <div v-else>Home not found</div>
</template>

This page fetches and renders content from a markdown file at the root of the content directory and uses the metadata for SEO.

Step 7: Set Up Default Layout

Create a default layout file:

// filepath: layouts/default.vue
<template>
  <div>
    <header class="bg-gray-900 text-white p-4">
      <div class="container mx-auto">
        <h1 class="text-xl font-bold">My Nuxt Blog</h1>
        <nav class="mt-2">
          <NuxtLink to="/" class="mr-4 hover:underline">Home</NuxtLink>
          <NuxtLink to="/blog" class="hover:underline">Blog</NuxtLink>
        </nav>
      </div>
    </header>

    <main class="container mx-auto p-4">
      <slot />
    </main>

    <footer class="bg-gray-200 p-4 mt-8">
      <div class="container mx-auto text-center text-gray-700">
        © {{ new Date().getFullYear() }} My Blog
      </div>
    </footer>
  </div>
</template>

Step 8: Configure Tailwind CSS

Create a CSS file to import Tailwind:

// filepath: assets/css/main.css
@import "tailwindcss";

Then, create a tailwind.config.js file:

// filepath: tailwind.config.js
module.exports = {
  content: [
    './components/**/*.{vue,js}',
    './layouts/**/*.vue',
    './pages/**/*.vue',
    './plugins/**/*.{js,ts}',
    './app.vue',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Finally, update nuxt.config.ts to include your CSS:

// filepath: nuxt.config.ts
import tailwindcss from "@tailwindcss/vite";

export default defineNuxtConfig({
  compatibilityDate: "2024-11-01",
  devtools: { enabled: true },
  modules: ['@nuxt/content'],
  css: ['~/assets/css/main.css'],
  vite: {
    plugins: [
      tailwindcss(),
    ],
  },
});

Step 9: Create Blog Content

Now it's time to add some content. Create a content directory in the root of your project and add markdown files:

// filepath: content/index.md
---
title: Welcome to My Blog
description: This is my personal blog built with Nuxt 3 and Content
---

# Welcome to My Blog

This is the homepage of my blog built with Nuxt 3 and the Content module.

## Recent Posts

Check out my latest articles in the blog section.
// filepath: content/blog/first-post.md
---
title: My First Blog Post
description: This is my first blog post using Nuxt Content
date: 2023-01-15
---

# My First Blog Post

Welcome to my first blog post! This post is written in Markdown and rendered using Nuxt Content.

## Features of Markdown

- **Bold text**
- *Italic text*
- [Links](https://nuxt.com)
- Lists like this one

// filepath: content/blog/second-post.md
---
title: My Second Blog Post
description: This is my second blog post using Nuxt Content
date: 2023-01-15
---

# My Second Blog Post

Welcome to my second blog post! This post is written in Markdown and rendered using Nuxt Content.

## Features of Markdown

- **Bold text**
- *Italic text*
- [Links](https://nuxt.com)
- Lists like this one

Step 10: Create a Blog Listing Page

Create a page to list all blog posts:

// filepath: pages/blog/index.vue
<script setup lang="ts">
const { data: posts } = await useAsyncData('blog', () => queryCollection('blog').all())
  
useSeoMeta({
  title: 'Blog',
  description: 'All blog posts'
})
</script>

<template>
  <div>
    <h1 class="text-3xl font-bold mb-6">Blog Posts</h1>
    
    <div v-if="posts && posts.length">
      <div v-for="post in posts" :key="post.path" class="mb-6 p-4 border border-gray-200 rounded-lg">
        <h2 class="text-xl font-semibold">
          <NuxtLink :to="post.path" class="text-blue-600 hover:underline">
            {{ post.title }}
          </NuxtLink>
        </h2>
        <div class="text-gray-500 text-sm mt-1" v-if="post.date">
          {{ new Date(post.date).toLocaleDateString() }}
        </div>
        <p class="mt-2">{{ post.description }}</p>
        <NuxtLink :to="post.path" class="text-blue-600 hover:underline mt-2 inline-block">
          Read more
        </NuxtLink>
      </div>
    </div>
    <p v-else>No blog posts found.</p>
  </div>
</template>

Step 11: Create a Blog Post Dynamic Page

Create a dynamic route to handle individual blog posts:

// filepath: pages/blog/[...slug].vue
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useAsyncData(route.path, () => {
  return queryCollection('blog').path(route.path).first()
})

if (!post.value) {
  throw createError({ statusCode: 404, message: 'Post not found' })
}

useSeoMeta({
  title: post.value.title,
  description: post.value.description
})
</script>

<template>
  <article>
    <ContentRenderer v-if="post" :value="post">
      <template #empty>
        <p>No content found.</p>
      </template>
    </ContentRenderer>
    
    <div class="mt-8">
      <NuxtLink to="/blog" class="text-blue-600 hover:underline">
        ← Back to all posts
      </NuxtLink>
    </div>
  </article>
</template>

Step 12: Running Your Blog

Start the development server:

npm run dev

Your blog should now be running at http://localhost:3000.

Conclusion

You now have a functional blog built with Nuxt 3, Content, and Tailwind CSS! This setup provides:

  • Fast performance with Nuxt 3's server-side rendering
  • Easy content management with markdown files
  • Beautiful styling with Tailwind CSS
  • Great SEO with automatic metadata handling

With this foundation in place, you can easily extend your blog with additional features to make it uniquely yours.

For more information, check out the official documentation: Nuxt 3, Nuxt Content, Query Collection, Tailwind CSS


Laravel's tap helper is a powerful utility that allows you to work with a value and return it without breaking the chain of operations. Let's explore how it works and how to use it effectively.

The TAP Function

At its core, the tap helper is a simple yet powerful function:

if (! function_exists('tap')) {
    /**
     * Call the given Closure with the given value then return the value.
     *
     * @template TValue
     *
     * @param  TValue  $value
     * @param  (callable(TValue): mixed)|null  $callback
     * @return ($callback is null ? \Illuminate\Support\HigherOrderTapProxy : TValue)
     */
    function tap($value, $callback = null)
    {
        if (is_null($callback)) {
            return new HigherOrderTapProxy($value);
        }

        $callback($value);

        return $value;
    }
}

The function does two key things:

  1. If no callback is provided, it returns a HigherOrderTapProxy instance
  2. If a callback is provided, it executes the callback with the value and returns the original value

Basic Usage

The tap helper allows you to "tap" into a chain of methods to perform operations on an object while still returning the original value.

$user = tap(User::first(), function($user) {
    $user->update(['last_login' => now()]);
});

Why Use TAP?

Without tap, you might write:

$user = User::first();
$user->update(['last_login' => now()]);
return $user;

With tap, it's more concise:

return tap(User::first(), function($user) {
    $user->update(['last_login' => now()]);
});

Real-World Examples

Example 1: File Operations

use Illuminate\Support\Facades\Storage;

$path = tap(Storage::put('path/to/file.txt', 'contents'), function() {
    Cache::forget('file-cache');
    Log::info('File was updated');
});
$post = tap(Post::create([
    'title' => 'My Post',
    'content' => 'Post content'
]), function($post) {
    $post->tags()->attach([1, 2, 3]);
    $post->user->notify(new PostCreated($post));
});

Example 3: Collection Manipulation

$processedUsers = tap(User::all(), function($users) {
    $users->each->notify(new WelcomeNotification);
    Cache::put('users', $users);
});

Using TAP with Arrow Functions

PHP 7.4+ allows for more concise syntax:

$user = tap(User::first(), fn($user) => $user->update(['last_login' => now()]));

The tap() Method on Collections

Laravel collections include a tap method:

collect([1, 2, 3])
    ->tap(function($collection) {
        Log::info('Count: ' . $collection->count());
    })
    ->filter(fn($value) => $value > 1)
    ->tap(function($collection) {
        Log::info('Filtered Count: ' . $collection->count());
    });

// Count: 3  
// Filtered Count: 2  

Testing with TAP

TAP is particularly useful in tests:

public function test_user_creation()
{
    $user = tap(User::factory()->create(), function($user) {
        $this->assertDatabaseHas('users', [
            'id' => $user->id,
            'email' => $user->email
        ]);
    });

    $this->actingAs($user);
}

Common Use Cases

  1. Logging Operations:
$response = tap($service->process(), function($result) {
    Log::info('Process completed', ['result' => $result]);
});
  1. Cache Operations:
$posts = tap(Post::all(), function($posts) {
    Cache::put('all_posts', $posts, now()->addDay());
});
  1. File Handling:
$file = tap(new UploadedFile(), function($file) {
    Storage::disk('public')->put('path/to/file', $file);
    event(new FileUploaded($file));
});

Best Practices

  1. Use tap when you need to perform side effects without breaking the chain
  2. Consider tap for cleaner testing code
  3. Combine with arrow functions for more concise syntax
  4. Use collection's tap method when working with collections

Conclusion

The tap helper is a powerful tool for writing cleaner, more maintainable code in Laravel. It's particularly useful for performing side effects while maintaining a fluent interface in your code.

For more information, check out the Laravel documentation.


Creating multiple Gutenberg blocks doesn't mean you need multiple plugins. Let's explore how to create a single plugin that can house multiple blocks efficiently.

Initial Setup

First, create your plugin's base structure using the @wordpress/create-block tool, we will be creating a plugin called "demo" for the sake of this tutorial, if you use a different name update references accordingly.

cd wp-content/plugins
npx @wordpress/create-block@latest demo

This creates your plugin's basic structure with one block. Now, let's add more blocks to it.

Adding Additional Blocks

Navigate to your plugin's source directory:

cd demo/src

Create additional blocks using the --no-plugin flag to avoid generating unnecessary plugin files, I have just called my two additional blocks "foo" and "bar" respectively, if you use different names update references accordingly

npx @wordpress/create-block@latest foo --no-plugin
npx @wordpress/create-block@latest bar --no-plugin

Plugin Structure

Your directory structure should now look like something like this:

demo/
├── src/
│   ├── demo/
│   ├── foo/
│   ├── bar/
└── demo.php

Update the demo.php file to enable loading the additional blocks

The generated demo.php file will look something like this

<?php
/**
 * Plugin Name:       Demo
 * Description:       Example block scaffolded with Create Block tool.
 * Version:           0.1.0
 * Requires at least: 6.7
 * Requires PHP:      7.4
 * Author:            The WordPress Contributors
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       demo
 *
 * @package CreateBlock
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

/**
 * Registers the block using the metadata loaded from the `block.json` file.
 * Behind the scenes, it registers also all assets so they can be enqueued
 * through the block editor in the corresponding context.
 *
 * @see https://developer.wordpress.org/reference/functions/register_block_type/
 */
function create_block_demo_block_init() {
    register_block_type( __DIR__ . '/build/demo' );
}
add_action( 'init', 'create_block_demo_block_init' );

Lets update the create_block_demo_block_init to allow it to use ALL our blocks

Replace

function create_block_demo_block_init() {
    register_block_type( __DIR__ . '/build/demo' );
}
add_action( 'init', 'create_block_demo_block_init' );

With this

function create_block_demo_blocks_init() {
    $blocks = array_map( function( $block ) {
        return [
            'name' => $block,
        ];
    }, array_filter( scandir( __DIR__ . '/build' ), function( $block ) {
        return $block[0] !== '.' && is_dir( __DIR__ . '/build/' . $block );
    } ) );

    foreach ( $blocks as $block ) {
        register_block_type( __DIR__  . '/build/' . $block['name'] );
    }
}
add_action( 'init', 'create_block_demo_blocks_init' );

Building Your Blocks

To build all blocks for production:

npm run build

For development with live reload:

npm run start

Block Usage

You can now navigate to the post and or page you would like to include your relevant block after altering the functionality as you desire

Using the blocks

When navigating to the front end you can now see the blocks which will display the content accordingly

Front end view of the blocks

This approach allows you to maintain multiple Gutenberg blocks within a single plugin, making maintenance and updates more manageable. Each block remains independent but shares the same build process and plugin infrastructure.

For more information, check out the @wordpress/create-block.