Navigation

Laravel

Building CLI Applications with Laravel Zero

Master Laravel Zero for building powerful CLI applications. Learn command creation, interactive interfaces, code generation, testing, optimization, and distribution. Transform Laravel into sophisticated command-line tools with elegant syntax.
Building CLI Applications with Laravel Zero

Laravel Zero transforms Laravel from a web framework into a powerful command-line application platform. By stripping away web-specific components and adding CLI-focused features, Laravel Zero enables developers to build sophisticated console applications with Laravel's elegant syntax and robust ecosystem, perfect for automation scripts, development tools, and system utilities.

Table Of Contents

Understanding Laravel Zero's Philosophy

Laravel Zero reimagines Laravel for the command line, maintaining the framework's core principles while optimizing for CLI environments. It provides a minimal, fast foundation that includes only essential components, allowing developers to add features as needed. This approach results in lightweight, single-file executables that can run anywhere PHP is available.

The framework excels at building tools that developers actually want to use – package managers, deployment scripts, code generators, and system administration utilities. With Laravel Zero, you can create professional CLI applications that rival those built with specialized CLI frameworks.

Getting Started with Laravel Zero

Installation and project setup are streamlined for rapid development:

Image 1

# Install Laravel Zero via Composer
composer create-project --prefer-dist laravel-zero/laravel-zero awesome-cli

cd awesome-cli

# Explore the project structure
tree -L 2

Laravel Zero's structure is optimized for CLI applications:

<?php

// app/Commands/AwesomeCommand.php
namespace App\Commands;

use LaravelZero\Framework\Commands\Command;
use Illuminate\Support\Facades\Http;

class AwesomeCommand extends Command
{
    protected $signature = 'awesome {name} {--format=json}';
    protected $description = 'Generate awesome greetings';

    public function handle(): int
    {
        $name = $this->argument('name');
        $format = $this->option('format');

        $greeting = $this->generateGreeting($name);

        if ($format === 'json') {
            $this->line(json_encode([
                'greeting' => $greeting,
                'timestamp' => now()->toISOString(),
                'format' => 'json'
            ], JSON_PRETTY_PRINT));
        } else {
            $this->info("🎉 {$greeting}");
        }

        return self::SUCCESS;
    }

    private function generateGreeting(string $name): string
    {
        $greetings = [
            "Hello, awesome {$name}!",
            "Greetings, magnificent {$name}!",
            "Salutations, incredible {$name}!",
            "Hey there, fantastic {$name}!"
        ];

        return $greetings[array_rand($greetings)];
    }
}

// config/commands.php - Register commands
return [
    'default' => 'awesome',
    'paths' => [app_path('Commands')],
    'add' => [
        // Additional commands
    ],
    'hidden' => [
        // Hidden commands
    ],
    'remove' => [
        // Commands to remove
    ],
];

You can also leverage Laravel Events and Listeners: Building Decoupled Applications for decoupled workflows, apply Advanced Eloquent Techniques and Optimizations in Laravel within your commands, and ensure reliability with Laravel Testing: Unit, Feature, and Integration Tests.

Advanced CLI Interactions

Create rich, interactive command-line experiences:

<?php

namespace App\Commands;

use LaravelZero\Framework\Commands\Command;
use Illuminate\Support\Collection;

class InteractiveSetupCommand extends Command
{
    protected $signature = 'setup {--force}';
    protected $description = 'Interactive application setup';

    public function handle(): int
    {
        $this->displayHeader();

        if (!$this->option('force') && !$this->confirmSetup()) {
            return self::FAILURE;
        }

        $config = $this->gatherConfiguration();
        $this->createConfigFile($config);
        $this->installDependencies($config);
        $this->finalizeSetup();

        return self::SUCCESS;
    }

    private function displayHeader(): void
    {
        $this->line('');
        $this->line('<fg=cyan>╔══════════════════════════════════════╗</>');
        $this->line('<fg=cyan>║</> <fg=white;options=bold>     Awesome CLI Setup Wizard      </> <fg=cyan>║</>');
        $this->line('<fg=cyan>╚══════════════════════════════════════╝</>');
        $this->line('');
    }

    private function confirmSetup(): bool
    {
        return $this->confirm(
            'This will configure your application. Continue?',
            true
        );
    }

    private function gatherConfiguration(): array
    {
        $config = [];

        // Text input with validation
        $config['app_name'] = $this->askWithValidation(
            'Application name',
            'required|string|min:3|max:50',
            'My Awesome App'
        );

        // Choice selection
        $config['environment'] = $this->choice(
            'Select environment',
            ['development', 'staging', 'production'],
            'development'
        );

        // Multiple choice with search
        $config['features'] = $this->multipleChoice(
            'Select features to enable',
            [
                'logging' => 'Advanced Logging',
                'cache' => 'Caching System',
                'database' => 'Database Support',
                'api' => 'HTTP API Client',
                'notifications' => 'Notifications',
            ]
        );

        // Secret input
        if (in_array('api', $config['features'])) {
            $config['api_key'] = $this->secret('Enter API key');
        }

        // Numeric input with bounds
        $config['timeout'] = $this->askNumeric(
            'Request timeout (seconds)',
            30,
            1,
            300
        );

        // Advanced configuration
        if ($this->confirm('Configure advanced options?', false)) {
            $config = array_merge($config, $this->gatherAdvancedConfig());
        }

        return $config;
    }

    private function multipleChoice(string $question, array $choices): array
    {
        $this->line("<fg=yellow>{$question}</>");
        
        $selected = [];
        foreach ($choices as $key => $label) {
            if ($this->confirm("  Enable {$label}?", false)) {
                $selected[] = $key;
            }
        }

        return $selected;
    }

    private function askNumeric(string $question, int $default, int $min, int $max): int
    {
        do {
            $value = (int) $this->ask("{$question} [{$min}-{$max}]", $default);
            
            if ($value < $min || $value > $max) {
                $this->error("Value must be between {$min} and {$max}");
                continue;
            }
            
            return $value;
        } while (true);
    }

    private function gatherAdvancedConfig(): array
    {
        return [
            'debug_mode' => $this->confirm('Enable debug mode?', false),
            'log_level' => $this->choice('Log level', [
                'debug', 'info', 'warning', 'error'
            ], 'info'),
            'max_memory' => $this->ask('Memory limit (MB)', '256'),
        ];
    }

    private function createConfigFile(array $config): void
    {
        $this->task('Creating configuration file', function () use ($config) {
            $configPath = base_path('config/app-config.json');
            
            file_put_contents(
                $configPath,
                json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
            );

            sleep(1); // Simulate work
            return true;
        });
    }

    private function installDependencies(array $config): void
    {
        if (empty($config['features'])) {
            return;
        }

        $this->info('Installing selected features...');

        $progressBar = $this->output->createProgressBar(count($config['features']));
        $progressBar->start();

        foreach ($config['features'] as $feature) {
            $this->installFeature($feature);
            $progressBar->advance();
            usleep(500000); // Simulate installation time
        }

        $progressBar->finish();
        $this->line('');
    }

    private function installFeature(string $feature): void
    {
        // Simulate feature installation
        $packages = [
            'logging' => 'monolog/monolog',
            'cache' => 'symfony/cache',
            'database' => 'illuminate/database',
            'api' => 'guzzlehttp/guzzle',
            'notifications' => 'laravel/slack-notification-channel',
        ];

        // In real implementation, you would run composer commands
        // or download/configure actual dependencies
    }

    private function finalizeSetup(): void
    {
        $this->line('');
        $this->info('✅ Setup completed successfully!');
        $this->line('');
        $this->line('<fg=green>Next steps:</>');
        $this->line('  • Run <fg=yellow>./app-name --help</> to see available commands');
        $this->line('  • Edit <fg=yellow>config/app-config.json</> to modify settings');
        $this->line('  • Check <fg=yellow>README.md</> for documentation');
        $this->line('');
    }
}

Building a Real-World Tool: Code Generator

Let's create a practical code generator tool:

<?php

namespace App\Commands;

use LaravelZero\Framework\Commands\Command;
use Illuminate\Support\Str;
use Illuminate\Filesystem\Filesystem;

class GenerateCommand extends Command
{
    protected $signature = 'generate {type} {name} {--namespace=App} {--force}';
    protected $description = 'Generate code files from templates';

    private Filesystem $files;
    private array $generators = [
        'class' => 'generateClass',
        'interface' => 'generateInterface',
        'trait' => 'generateTrait',
        'enum' => 'generateEnum',
        'test' => 'generateTest',
        'config' => 'generateConfig',
    ];

    public function __construct(Filesystem $files)
    {
        parent::__construct();
        $this->files = $files;
    }

    public function handle(): int
    {
        $type = $this->argument('type');
        $name = $this->argument('name');

        if (!isset($this->generators[$type])) {
            $this->error("Unknown generator type: {$type}");
            $this->line('Available types: ' . implode(', ', array_keys($this->generators)));
            return self::FAILURE;
        }

        $method = $this->generators[$type];
        return $this->$method($name);
    }

    private function generateClass(string $name): int
    {
        $className = Str::studly($name);
        $namespace = $this->option('namespace');
        $path = $this->getOutputPath($className, 'php');

        if ($this->files->exists($path) && !$this->option('force')) {
            if (!$this->confirm("File {$path} exists. Overwrite?")) {
                return self::FAILURE;
            }
        }

        $template = $this->loadTemplate('class');
        $content = $this->processTemplate($template, [
            'namespace' => $namespace,
            'className' => $className,
            'timestamp' => now()->format('Y-m-d H:i:s'),
            'author' => $this->getUserName(),
        ]);

        $this->ensureDirectoryExists(dirname($path));
        $this->files->put($path, $content);

        $this->info("✅ Class created: {$path}");
        return self::SUCCESS;
    }

    private function generateInterface(string $name): int
    {
        $interfaceName = Str::studly($name) . 'Interface';
        $namespace = $this->option('namespace');
        $path = $this->getOutputPath($interfaceName, 'php');

        $methods = $this->gatherInterfaceMethods();
        
        $template = $this->loadTemplate('interface');
        $content = $this->processTemplate($template, [
            'namespace' => $namespace,
            'interfaceName' => $interfaceName,
            'methods' => $this->generateMethodSignatures($methods),
            'timestamp' => now()->format('Y-m-d H:i:s'),
        ]);

        $this->ensureDirectoryExists(dirname($path));
        $this->files->put($path, $content);

        $this->info("✅ Interface created: {$path}");
        return self::SUCCESS;
    }

    private function generateTest(string $name): int
    {
        $testName = Str::studly($name) . 'Test';
        $namespace = 'Tests\\Unit';
        $path = $this->getOutputPath($testName, 'php', 'tests/Unit');

        // Interactive test configuration
        $testType = $this->choice('Test type', ['unit', 'feature', 'integration'], 'unit');
        $withMocks = $this->confirm('Include mock examples?', false);
        $withDataProvider = $this->confirm('Include data provider?', false);

        $template = $this->loadTemplate('test');
        $content = $this->processTemplate($template, [
            'namespace' => $namespace,
            'testName' => $testName,
            'testType' => $testType,
            'withMocks' => $withMocks,
            'withDataProvider' => $withDataProvider,
            'className' => str_replace('Test', '', $testName),
        ]);

        $this->ensureDirectoryExists(dirname($path));
        $this->files->put($path, $content);

        $this->info("✅ Test created: {$path}");
        $this->line("Run with: <fg=yellow>phpunit {$path}</>");
        
        return self::SUCCESS;
    }

    private function generateConfig(string $name): int
    {
        $configName = Str::snake($name);
        $path = $this->getOutputPath($configName, 'php', 'config');

        $configType = $this->choice('Configuration type', [
            'array' => 'PHP Array',
            'class' => 'Configuration Class',
            'env' => 'Environment Variables',
        ], 'array');

        $template = $this->loadTemplate("config.{$configType}");
        $content = $this->processTemplate($template, [
            'configName' => $configName,
            'className' => Str::studly($configName) . 'Config',
            'variables' => $this->gatherConfigVariables(),
        ]);

        $this->ensureDirectoryExists(dirname($path));
        $this->files->put($path, $content);

        $this->info("✅ Configuration created: {$path}");
        return self::SUCCESS;
    }

    private function gatherInterfaceMethods(): array
    {
        $methods = [];
        
        $this->info('Define interface methods (press enter with empty name to finish):');
        
        while (true) {
            $methodName = $this->ask('Method name');
            
            if (empty($methodName)) {
                break;
            }

            $returnType = $this->ask('Return type', 'mixed');
            $parameters = $this->gatherMethodParameters();

            $methods[] = [
                'name' => $methodName,
                'returnType' => $returnType,
                'parameters' => $parameters,
            ];
        }

        return $methods;
    }

    private function gatherMethodParameters(): array
    {
        $parameters = [];
        
        while (true) {
            $paramName = $this->ask('Parameter name (empty to finish)');
            
            if (empty($paramName)) {
                break;
            }

            $paramType = $this->ask('Parameter type', 'mixed');
            $hasDefault = $this->confirm('Has default value?', false);
            $defaultValue = $hasDefault ? $this->ask('Default value', 'null') : null;

            $parameters[] = [
                'name' => $paramName,
                'type' => $paramType,
                'default' => $defaultValue,
            ];
        }

        return $parameters;
    }

    private function gatherConfigVariables(): array
    {
        $variables = [];
        
        $this->info('Define configuration variables:');
        
        while (true) {
            $varName = $this->ask('Variable name (empty to finish)');
            
            if (empty($varName)) {
                break;
            }

            $varType = $this->choice('Variable type', [
                'string', 'int', 'bool', 'array', 'float'
            ], 'string');
            
            $defaultValue = $this->ask('Default value', 'null');
            $description = $this->ask('Description', '');

            $variables[] = [
                'name' => $varName,
                'type' => $varType,
                'default' => $defaultValue,
                'description' => $description,
            ];
        }

        return $variables;
    }

    private function generateMethodSignatures(array $methods): string
    {
        $signatures = [];
        
        foreach ($methods as $method) {
            $params = [];
            
            foreach ($method['parameters'] as $param) {
                $paramStr = $param['type'] . ' $' . $param['name'];
                
                if ($param['default'] !== null) {
                    $paramStr .= ' = ' . $param['default'];
                }
                
                $params[] = $paramStr;
            }

            $signature = sprintf(
                '    public function %s(%s): %s;',
                $method['name'],
                implode(', ', $params),
                $method['returnType']
            );

            $signatures[] = $signature;
        }

        return implode("\n\n", $signatures);
    }

    private function loadTemplate(string $templateName): string
    {
        $templatePath = resource_path("templates/{$templateName}.stub");
        
        if (!$this->files->exists($templatePath)) {
            throw new \RuntimeException("Template not found: {$templatePath}");
        }

        return $this->files->get($templatePath);
    }

    private function processTemplate(string $template, array $variables): string
    {
        $content = $template;
        
        foreach ($variables as $key => $value) {
            if (is_array($value)) {
                $value = $this->processArrayVariable($value);
            }
            
            $content = str_replace("{{ {$key} }}", $value, $content);
        }

        return $content;
    }

    private function processArrayVariable(array $data): string
    {
        // Convert array data to appropriate string representation
        return var_export($data, true);
    }

    private function getOutputPath(string $name, string $extension, string $directory = 'app'): string
    {
        return base_path("{$directory}/{$name}.{$extension}");
    }

    private function ensureDirectoryExists(string $path): void
    {
        if (!$this->files->isDirectory($path)) {
            $this->files->makeDirectory($path, 0755, true);
        }
    }

    private function getUserName(): string
    {
        return trim(shell_exec('git config user.name 2>/dev/null')) ?: 'Unknown';
    }
}

// Template files (store in resources/templates/)

// resources/templates/class.stub
/*
<?php

namespace {{ namespace }};

/**
 * Class {{ className }}
 * 
 * Generated on: {{ timestamp }}
 * Author: {{ author }}
 */
class {{ className }}
{
    public function __construct()
    {
        //
    }
}
*/

// resources/templates/interface.stub
/*
<?php

namespace {{ namespace }};

/**
 * Interface {{ interfaceName }}
 * 
 * Generated on: {{ timestamp }}
 */
interface {{ interfaceName }}
{
{{ methods }}
}
*/

Package Management and Distribution

Create distributable Laravel Zero applications:

<?php

namespace App\Commands;

use LaravelZero\Framework\Commands\Command;
use Illuminate\Support\Facades\Http;

class BuildCommand extends Command
{
    protected $signature = 'build {--optimize} {--target=all}';
    protected $description = 'Build distributable application';

    public function handle(): int
    {
        $this->info('🔨 Building application...');

        $this->optimizeApplication();
        $this->createDistributableFiles();
        $this->generateDocumentation();
        
        if ($this->option('target') === 'all' || $this->option('target') === 'phar') {
            $this->buildPharArchive();
        }

        $this->info('✅ Build completed successfully!');
        return self::SUCCESS;
    }

    private function optimizeApplication(): void
    {
        if (!$this->option('optimize')) {
            return;
        }

        $this->task('Optimizing application', function () {
            // Clear caches
            $this->call('cache:clear');
            
            // Optimize autoloader
            shell_exec('composer dump-autoload --optimize --no-dev');
            
            // Remove development dependencies
            shell_exec('composer install --no-dev --optimize-autoloader');
            
            return true;
        });
    }

    private function createDistributableFiles(): void
    {
        $this->task('Creating distribution files', function () {
            $buildDir = base_path('build');
            
            if (!is_dir($buildDir)) {
                mkdir($buildDir, 0755, true);
            }

            // Copy application files
            $this->copyFiles([
                'app' => 'build/app',
                'config' => 'build/config',
                'vendor' => 'build/vendor',
                'bootstrap' => 'build/bootstrap',
            ]);

            // Create executable script
            $this->createExecutableScript();
            
            return true;
        });
    }

    private function copyFiles(array $paths): void
    {
        foreach ($paths as $source => $destination) {
            if (is_dir($source)) {
                shell_exec("cp -r {$source} {$destination}");
            } elseif (file_exists($source)) {
                copy($source, $destination);
            }
        }
    }

    private function createExecutableScript(): void
    {
        $script = <<<'SCRIPT'
#!/usr/bin/env php
<?php

if (file_exists(__DIR__.'/vendor/autoload.php')) {
    require __DIR__.'/vendor/autoload.php';
} else {
    require __DIR__.'/../../autoload.php';
}

$app = new LaravelZero\Framework\Application(
    dirname(__DIR__)
);

$kernel = $app->make(LaravelZero\Framework\Kernel::class);

$status = $kernel->handle(
    $input = new Symfony\Component\Console\Input\ArgvInput,
    new Symfony\Component\Console\Output\ConsoleOutput
);

$kernel->terminate($input, $status);

exit($status);
SCRIPT;

        file_put_contents('build/app-executable', $script);
        chmod('build/app-executable', 0755);
    }

    private function buildPharArchive(): void
    {
        $this->task('Building PHAR archive', function () {
            $pharPath = base_path('build/app.phar');
            
            if (file_exists($pharPath)) {
                unlink($pharPath);
            }

            $phar = new \Phar($pharPath, 0, 'app.phar');
            $phar->setSignatureAlgorithm(\Phar::SHA1);
            
            $phar->startBuffering();
            
            // Add application files
            $this->addDirectoryToPhar($phar, 'build/app', 'app');
            $this->addDirectoryToPhar($phar, 'build/vendor', 'vendor');
            $this->addDirectoryToPhar($phar, 'build/config', 'config');
            $this->addDirectoryToPhar($phar, 'build/bootstrap', 'bootstrap');
            
            // Set stub
            $phar->setStub($this->getPharStub());
            
            $phar->stopBuffering();
            
            chmod($pharPath, 0755);
            
            return true;
        });
    }

    private function addDirectoryToPhar(\Phar $phar, string $directory, string $localName): void
    {
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($directory)
        );

        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $relativePath = str_replace($directory . '/', '', $file->getPathname());
                $phar->addFile($file->getPathname(), $localName . '/' . $relativePath);
            }
        }
    }

    private function getPharStub(): string
    {
        return <<<'STUB'
#!/usr/bin/env php
<?php
Phar::mapPhar('app.phar');
require 'phar://app.phar/bootstrap/app.php';
__HALT_COMPILER();
STUB;
    }

    private function generateDocumentation(): void
    {
        $this->task('Generating documentation', function () {
            $commands = $this->getAvailableCommands();
            $readme = $this->generateReadme($commands);
            
            file_put_contents('build/README.md', $readme);
            
            return true;
        });
    }

    private function getAvailableCommands(): array
    {
        $commands = [];
        
        foreach (glob(app_path('Commands/*.php')) as $file) {
            $className = pathinfo($file, PATHINFO_FILENAME);
            $class = "App\\Commands\\{$className}";
            
            if (class_exists($class)) {
                $reflection = new \ReflectionClass($class);
                $instance = $reflection->newInstance();
                
                if (property_exists($instance, 'signature') && property_exists($instance, 'description')) {
                    $commands[] = [
                        'name' => $instance->getName(),
                        'signature' => $instance->signature,
                        'description' => $instance->description,
                    ];
                }
            }
        }

        return $commands;
    }

    private function generateReadme(array $commands): string
    {
        $appName = config('app.name', 'Laravel Zero App');
        $version = config('app.version', '1.0.0');
        
        $readme = "# {$appName}\n\n";
        $readme .= "Version: {$version}\n\n";
        $readme .= "## Installation\n\n";
        $readme .= "Download the `app.phar` file and make it executable:\n\n";
        $readme .= "```bash\n";
        $readme .= "chmod +x app.phar\n";
        $readme .= "./app.phar --help\n";
        $readme .= "```\n\n";
        $readme .= "## Available Commands\n\n";
        
        foreach ($commands as $command) {
            $readme .= "### {$command['name']}\n\n";
            $readme .= "{$command['description']}\n\n";
            $readme .= "```bash\n";
            $readme .= "./{$appName} {$command['signature']}\n";
            $readme .= "```\n\n";
        }

        return $readme;
    }
}

Testing Laravel Zero Applications

Implement comprehensive testing strategies:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Storage;

class CodeGeneratorTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        
        // Setup test filesystem
        Storage::fake('local');
        
        // Create mock templates directory
        $this->createMockTemplates();
    }

    public function test_generates_class_successfully(): void
    {
        $this->artisan('generate class TestClass --namespace=App\\Models')
            ->expectsQuestion('File exists. Overwrite?', false)
            ->assertExitCode(0)
            ->expectsOutput('✅ Class created:');
    }

    public function test_interactive_interface_generation(): void
    {
        $this->artisan('generate interface PaymentProcessor')
            ->expectsQuestion('Method name', 'process')
            ->expectsQuestion('Return type', 'bool')
            ->expectsQuestion('Parameter name (empty to finish)', 'amount')
            ->expectsQuestion('Parameter type', 'float')
            ->expectsQuestion('Has default value?', false)
            ->expectsQuestion('Parameter name (empty to finish)', '')
            ->expectsQuestion('Method name', '')
            ->assertExitCode(0);
    }

    public function test_handles_invalid_generator_type(): void
    {
        $this->artisan('generate invalid TestName')
            ->assertExitCode(1)
            ->expectsOutput('Unknown generator type: invalid');
    }

    public function test_build_command_creates_distribution(): void
    {
        $this->artisan('build --optimize')
            ->assertExitCode(0)
            ->expectsOutput('🔨 Building application...')
            ->expectsOutput('✅ Build completed successfully!');

        // Assert build directory exists
        $this->assertTrue(is_dir(base_path('build')));
        
        // Assert executable was created
        $this->assertTrue(file_exists(base_path('build/app-executable')));
    }

    public function test_setup_command_interactive_flow(): void
    {
        $this->artisan('setup')
            ->expectsConfirmation('This will configure your application. Continue?', 'yes')
            ->expectsQuestion('Application name', 'Test App')
            ->expectsChoice('Select environment', 'development')
            ->expectsConfirmation('Enable Advanced Logging?', 'yes')
            ->expectsConfirmation('Enable Caching System?', 'no')
            ->expectsConfirmation('Configure advanced options?', 'no')
            ->assertExitCode(0);
            
        // Assert config file was created
        $this->assertTrue(file_exists(base_path('config/app-config.json')));
    }

    private function createMockTemplates(): void
    {
        $templates = [
            'class' => '<?php\n\nnamespace {{ namespace }};\n\nclass {{ className }}\n{\n    //\n}',
            'interface' => '<?php\n\nnamespace {{ namespace }};\n\ninterface {{ interfaceName }}\n{\n{{ methods }}\n}',
            'test' => '<?php\n\nnamespace {{ namespace }};\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass {{ testName }} extends TestCase\n{\n    //\n}',
        ];

        foreach ($templates as $name => $content) {
            $path = resource_path("templates/{$name}.stub");
            
            if (!is_dir(dirname($path))) {
                mkdir(dirname($path), 0755, true);
            }
            
            file_put_contents($path, $content);
        }
    }
}

// Unit test for utility classes
namespace Tests\Unit;

use Tests\TestCase;
use App\Services\TemplateProcessor;

class TemplateProcessorTest extends TestCase
{
    private TemplateProcessor $processor;

    protected function setUp(): void
    {
        parent::setUp();
        $this->processor = new TemplateProcessor();
    }

    public function test_processes_simple_variables(): void
    {
        $template = 'Hello {{ name }}, welcome to {{ app }}!';
        $variables = ['name' => 'John', 'app' => 'Laravel Zero'];
        
        $result = $this->processor->process($template, $variables);
        
        $this->assertEquals('Hello John, welcome to Laravel Zero!', $result);
    }

    public function test_handles_array_variables(): void
    {
        $template = 'Methods: {{ methods }}';
        $variables = ['methods' => ['create', 'update', 'delete']];
        
        $result = $this->processor->process($template, $variables);
        
        $this->assertStringContainsString('create', $result);
        $this->assertStringContainsString('update', $result);
        $this->assertStringContainsString('delete', $result);
    }

    public function test_ignores_missing_variables(): void
    {
        $template = 'Hello {{ name }}, {{ missing }} variable';
        $variables = ['name' => 'John'];
        
        $result = $this->processor->process($template, $variables);
        
        $this->assertEquals('Hello John, {{ missing }} variable', $result);
    }
}

Performance and Optimization

Optimize Laravel Zero applications for speed:

<?php

namespace App\Commands;

use LaravelZero\Framework\Commands\Command;
use Illuminate\Support\Facades\Cache;

class OptimizeCommand extends Command
{
    protected $signature = 'optimize {--clear}';
    protected $description = 'Optimize application performance';

    public function handle(): int
    {
        if ($this->option('clear')) {
            $this->clearOptimizations();
        } else {
            $this->runOptimizations();
        }

        return self::SUCCESS;
    }

    private function runOptimizations(): void
    {
        $this->info('🚀 Optimizing application...');

        $optimizations = [
            'Optimizing configuration' => [$this, 'optimizeConfig'],
            'Optimizing routes' => [$this, 'optimizeRoutes'],
            'Optimizing autoloader' => [$this, 'optimizeAutoloader'],
            'Caching templates' => [$this, 'cacheTemplates'],
            'Optimizing dependencies' => [$this, 'optimizeDependencies'],
        ];

        foreach ($optimizations as $description => $callback) {
            $this->task($description, $callback);
        }

        $this->info('✅ Optimization completed!');
    }

    private function optimizeConfig(): bool
    {
        // Cache configuration
        $this->call('config:cache');
        
        // Remove unused config files
        $configFiles = glob(config_path('*.php'));
        $usedConfigs = ['app', 'cache', 'logging'];
        
        foreach ($configFiles as $file) {
            $basename = basename($file, '.php');
            if (!in_array($basename, $usedConfigs)) {
                // Move to backup location instead of deleting
                rename($file, $file . '.backup');
            }
        }

        return true;
    }

    private function optimizeRoutes(): bool
    {
        // For Laravel Zero, optimize command discovery
        $commandFiles = glob(app_path('Commands/*.php'));
        $commandCache = [];

        foreach ($commandFiles as $file) {
            $className = basename($file, '.php');
            $commandCache[$className] = [
                'file' => $file,
                'modified' => filemtime($file),
            ];
        }

        Cache::forever('command_cache', $commandCache);
        return true;
    }

    private function optimizeAutoloader(): bool
    {
        shell_exec('composer dump-autoload --optimize');
        return true;
    }

    private function cacheTemplates(): bool
    {
        $templateDir = resource_path('templates');
        
        if (!is_dir($templateDir)) {
            return true;
        }

        $templates = glob($templateDir . '/*.stub');
        $templateCache = [];

        foreach ($templates as $template) {
            $name = basename($template, '.stub');
            $templateCache[$name] = file_get_contents($template);
        }

        Cache::forever('template_cache', $templateCache);
        return true;
    }

    private function optimizeDependencies(): bool
    {
        // Remove development dependencies in production
        if (app()->environment('production')) {
            shell_exec('composer install --no-dev --optimize-autoloader');
        }

        return true;
    }

    private function clearOptimizations(): void
    {
        $this->info('🧹 Clearing optimizations...');

        $operations = [
            'Clearing configuration cache' => function () {
                $this->call('config:clear');
                return true;
            },
            'Clearing command cache' => function () {
                Cache::forget('command_cache');
                return true;
            },
            'Clearing template cache' => function () {
                Cache::forget('template_cache');
                return true;
            },
            'Restoring config backups' => function () {
                $backups = glob(config_path('*.php.backup'));
                foreach ($backups as $backup) {
                    rename($backup, str_replace('.backup', '', $backup));
                }
                return true;
            },
        ];

        foreach ($operations as $description => $callback) {
            $this->task($description, $callback);
        }

        $this->info('✅ Optimizations cleared!');
    }
}

// Performance monitoring
class PerformanceProfiler
{
    private float $startTime;
    private int $startMemory;
    private array $checkpoints = [];

    public function start(): void
    {
        $this->startTime = microtime(true);
        $this->startMemory = memory_get_usage(true);
    }

    public function checkpoint(string $name): void
    {
        $this->checkpoints[$name] = [
            'time' => microtime(true) - $this->startTime,
            'memory' => memory_get_usage(true) - $this->startMemory,
            'peak_memory' => memory_get_peak_usage(true),
        ];
    }

    public function report(): array
    {
        return [
            'total_time' => microtime(true) - $this->startTime,
            'total_memory' => memory_get_usage(true) - $this->startMemory,
            'peak_memory' => memory_get_peak_usage(true),
            'checkpoints' => $this->checkpoints,
        ];
    }
}

Conclusion

Laravel Zero transforms Laravel into a powerful CLI application framework that maintains the elegance and functionality developers love while optimizing for command-line environments. By leveraging Laravel's ecosystem within a CLI context, developers can build sophisticated tools that rival those created with specialized CLI frameworks.

The framework's strength lies in its ability to make complex CLI applications feel natural to Laravel developers. From interactive setup wizards to distributable PHAR archives, Laravel Zero provides all the tools needed to create professional command-line applications. Whether building internal development tools, system utilities, or standalone applications, Laravel Zero offers the perfect balance of power and simplicity.

Related Articles

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel