Laravel Microservices with Lumen Tutorial


 In this tutorial, we will create a Laravel command that sets up a new Lumen microservice with Docker support. Additionally, we'll ensure Laravel Sail is installed as part of the process. Here's a step-by-step guide:

Step 1: Install Laravel Sail

First, install Laravel Sail if you haven't already. Sail is a CLI for interacting with Laravel's Docker environment.

composer require laravel/sail --dev php artisan sail:install

After installing Sail, you can start your application with Docker using:

./vendor/bin/sail up

Step 2: Create the Command

Create a new console command in Laravel. Run the following artisan command:

php artisan make:command CreateLumenService

This command will generate a new file in app/Console/Commands/CreateLumenService.php. Replace the content with the following:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Symfony\Component\Process\Process;

class CreateLumenService extends Command
{
    protected $signature = 'make:lumen-service {name} {port}';
    protected $description = 'Create a new Lumen service';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        $name = $this->argument('name');
        $port = $this->argument('port');
        $serviceName = $name . '-service';
        $servicePath = base_path("app/Services/$name");

        $process = new Process(['composer', 'create-project', '--prefer-dist', 'laravel/lumen', $servicePath]);
        $process->run();

        if (!$process->isSuccessful()) {
            $this->error('Error creating Lumen service');
            return 1;
        }

        $appKey = 'base64:' . base64_encode(random_bytes(32));
        $envPath = "$servicePath/.env";
        $envContent = File::get($envPath);
        $envContent = preg_replace('/^APP_NAME=.*$/m', "APP_NAME=\"$name\"", $envContent);
        $envContent = preg_replace('/^APP_KEY=.*$/m', "APP_KEY=$appKey", $envContent);
        $envContent = preg_replace('/^APP_PORT=.*$/m', "APP_PORT=$port", $envContent);
        $envContent = preg_replace('/^APP_URL=.*$/m', "APP_URL=http://localhost:$port", $envContent);
        $envContent = preg_replace('/^APP_TIMEZONE=.*$/m', 'APP_TIMEZONE=Asia/Karachi', $envContent);

        if (!preg_match('/^APP_KEY=.*$/m', $envContent)) {
            $envContent .= "\nAPP_KEY=$appKey";
        }
        if (!preg_match('/^APP_TIMEZONE=.*$/m', $envContent)) {
            $envContent .= "\nAPP_TIMEZONE=Asia/Karachi";
        }
        if (!preg_match('/^APP_PORT=.*$/m', $envContent)) {
            $envContent .= "\nAPP_PORT=$port";
        }
        if (!preg_match('/^APP_URL=.*$/m', $envContent)) {
            $envContent .= "\nAPP_URL=http://localhost:$port";
        }

        File::put($envPath, $envContent);

        $dockerfilePath = "$servicePath/Dockerfile";
        $dockerfileContent = <<<DOCKERFILE
        # Use the official PHP image as the base image
        FROM php:8.1-fpm

        # Set working directory
        WORKDIR /var/www

        # Install dependencies
        RUN apt-get update && apt-get install -y \\
            build-essential \\
            libpng-dev \\
            libjpeg62-turbo-dev \\
            libfreetype6-dev \\
            libonig-dev \\
            libxml2-dev \\
            zip \\
            unzip \\
            git \\
            curl

        # Clear cache
        RUN apt-get clean && rm -rf /var/lib/apt/lists/*

        # Install extensions
        RUN docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd

        # Install Composer
        COPY --from=composer:2.1 /usr/bin/composer /usr/bin/composer

        # Copy existing application directory contents
        COPY . /var/www

        # Copy existing application directory permissions
        COPY --chown=www-data:www-data . /var/www

        # Change current user to www
        USER www-data

        # Expose port 9000 and start php-fpm server
        EXPOSE $port
        CMD ["php-fpm"]
        DOCKERFILE;

        File::put($dockerfilePath, $dockerfileContent);

        $composePath = base_path('docker-compose.yml');
        $composeContent = File::get($composePath);

        $newService = "
        $serviceName:
            build:
                context: ./app/Services/$name
                dockerfile: Dockerfile
            container_name: $serviceName
            restart: unless-stopped
            working_dir: /var/www/html
            volumes:
                - ./app/Services/$name:/var/www/html
            networks:
                - sail
            ports:
                - '$port:$port'  # Mapping port $port in the container to $port on the host
            command: php -S 0.0.0.0:$port -t public
        ";

        // Insert the new service after the last service
        $servicesEnd = strrpos($composeContent, '  mysql:');
        $composeContent = substr_replace($composeContent, $newService, $servicesEnd, 0);

        File::put($composePath, $composeContent);

        $this->info("Lumen service '$name' created on port $port and docker-compose.yml updated");
        return 0;
    }
}

Explanation

  1. Namespace and Imports: We declare the namespace and import necessary classes. We use Command from Laravel, File for file operations, and Process for running shell commands.

  2. Command Signature and Description:

    protected $signature = 'make:lumen-service {name} {port}';
    protected $description = 'Create a new Lumen service';

    Defines the command name and description.

  3. Constructor: Calls the parent constructor.

    public function __construct()
    { parent::__construct(); }
  4. Handle Method:

    • Arguments:
      $name = $this->argument('name');
      $port = $this->argument('port');
      Retrieves the name and port arguments.
    • Service Path:
      $servicePath = base_path("app/Services/$name");
      Determines the service path.
    • Create Lumen Project:
      $process = new Process(['composer', 'create-project', '--prefer-dist', 'laravel/lumen', $servicePath]);
      $process->run(); if (!$process->isSuccessful()) { $this->error('Error creating Lumen service'); return 1; }
      Runs a shell command to create a new Lumen project. Handles errors if the command fails.
  5. Generate App Key and Update .env:

    $appKey = 'base64:' . base64_encode(random_bytes(32));
    $envPath = "$servicePath/.env"; $envContent = File::get($envPath); $envContent = preg_replace('/^APP_NAME=.*$/m', "APP_NAME=\"$name\"", $envContent); $envContent = preg_replace('/^APP_KEY=.*$/m', "APP_KEY=$appKey", $envContent); $envContent = preg_replace('/^APP_PORT=.*$/m', "APP_PORT=$port", $envContent); $envContent = preg_replace('/^APP_URL=.*$/m', "APP_URL=http://localhost:$port", $envContent); $envContent = preg_replace('/^APP_TIMEZONE=.*$/m', 'APP_TIMEZONE=Asia/Karachi', $envContent); if (!preg_match('/^APP_KEY=.*$/m', $envContent)) { $envContent .= "\nAPP_KEY=$appKey"; } if (!preg_match('/^APP_TIMEZONE=.*$/m', $envContent)) { $envContent .= "\nAPP_TIMEZONE=Asia/Karachi"; } if (!preg_match('/^APP_PORT=.*$/m', $envContent)) { $envContent .= "\nAPP_PORT=$port"; } if (!preg_match('/^APP_URL=.*$/m', $envContent)) { $envContent .= "\nAPP_URL=http://localhost:$port"; } File::put($envPath, $envContent);

    Generates a random APP_KEY, updates the .env file with the provided values, and ensures necessary environment variables are set.

  6. Create Dockerfile:

    $dockerfilePath = "$servicePath/Dockerfile";
    $dockerfileContent = <<<DOCKERFILE # Use the official PHP image as the base image FROM php:8.1-fpm # Set working directory WORKDIR /var/www # Install dependencies RUN apt-get update && apt-get install -y \\ build-essential \\ libpng-dev \\ libjpeg62-turbo-dev \\ libfreetype6-dev \\ libonig-dev \\ libxml2-dev \\ zip \\ unzip \\ git \\ curl # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Install extensions RUN docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd # Install Composer COPY --from=composer:2.1 /usr/bin/composer /usr/bin/composer # Copy existing application directory contents COPY . /var/www # Copy existing application directory permissions COPY --chown=www-data:www-data . /var/www # Change current user to www USER www-data # Expose port 9000 and start php-fpm server EXPOSE $port CMD ["php-fpm"] DOCKERFILE; File::put($dockerfilePath, $dockerfileContent);

    Creates a Dockerfile for the Lumen service, setting up PHP and necessary extensions.

  7. Update docker-compose.yml:

    $composePath = base_path('docker-compose.yml');
    $composeContent = File::get($composePath); $newService = " $serviceName: build: context: ./app/Services/$name dockerfile: Dockerfile container_name: $serviceName restart: unless-stopped working_dir: /var/www/html volumes: - ./app/Services/$name:/var/www/html networks: - sail ports: - '$port:$port' # Mapping port $port in the container to $port on the host command: php -S 0.0.0.0:$port -t public "; // Insert the new service after the last service $servicesEnd = strrpos($composeContent, ' mysql:'); $composeContent = substr_replace($composeContent, $newService, $servicesEnd, 0); File::put($composePath, $composeContent);

    Adds the new Lumen service to docker-compose.yml with the specified port.

  8. Command Success Message:

    $this->info("Lumen service '$name' created on port $port and docker-compose.yml updated");
    return 0;

    Displays a success message after creating the Lumen service and updating docker-compose.yml.

Conclusion

Following this tutorial, you can create a new Lumen microservice with Docker support in your Laravel project using a custom artisan command. This setup helps streamline the creation and management of microservices in a Laravel-based environment.

No comments:

Post a Comment