定制 neuron-php/cli 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

neuron-php/cli

最新稳定版本:0.8.9

Composer 安装命令:

composer require neuron-php/cli

包简介

Unified command-line interface for the Neuron PHP framework

README 文档

README

CI codecov

Neuron CLI Component

A unified command-line interface for the Neuron PHP framework that provides a modern, extensible CLI tool for all Neuron components.

Features

  • Unified CLI Interface - Single entry point for all component commands
  • Automatic Command Discovery - Auto-detects commands from installed components
  • Rich Terminal Output - Colored output, tables, and progress bars
  • Zero Configuration - Works out of the box with any Neuron component
  • Extensible Architecture - Easy to add custom commands

Requirements

  • PHP 8.4 or higher
  • Composer

Installation

Local Installation (Recommended)

composer require neuron-php/cli

After installation, the CLI will be available at:

./vendor/bin/neuron

Global Installation

composer global require neuron-php/cli

Make sure your global Composer bin directory is in your PATH, then:

neuron version

Basic Usage

List All Commands

neuron list

Get Help

# General help
neuron --help

# Help for specific command
neuron help make:controller

Config Export

Export a YAML config as environment-style pairs for production.

# Export project config to .env production file
./vendor/bin/neuron config:env --config=config/neuron.yaml > .env.production

# Only export selected categories
./vendor/bin/neuron config:env --category=site,cache

# Shell format (export lines)
./vendor/bin/neuron config:env --format=shell

# Quoting control: auto|always|never (default: auto)
./vendor/bin/neuron config:env --quote=always

Notes:

  • Keys become UPPER_SNAKE_CASE using CATEGORY_NAME from YAML.
  • Nested and array values are flattened (e.g., ARRAY_KEY_0, ARRAY_KEY_1).
  • Booleans and nulls are stringified appropriately for env files.

For Component Developers

The CLI component provides two ways for your component to register commands:

Method 1: CLI Provider Class (Recommended)

This is the preferred method as it keeps all command registrations in one place.

  1. Update your component's composer.json:
{
    "name": "neuron-php/your-component",
    "extra": {
        "neuron": {
            "cli-provider": "Neuron\\YourComponent\\Cli\\CommandProvider"
        }
    }
}
  1. Create the provider class:
<?php

namespace Neuron\YourComponent\Cli;

class CommandProvider
{
    public static function register($app): void
    {
        // Register your commands
        $app->register('component:command1', Command1::class);
        $app->register('component:command2', Command2::class);
        $app->register('component:command3', Command3::class);
    }
}

Method 2: Direct Registration

For simpler cases, you can register commands directly in composer.json:

{
    "name": "neuron-php/your-component",
    "extra": {
        "neuron": {
            "commands": {
                "component:command": "Neuron\\YourComponent\\Commands\\YourCommand",
                "component:another": "Neuron\\YourComponent\\Commands\\AnotherCommand"
            }
        }
    }
}

Creating Command Classes

All commands must extend the base Command class:

<?php

namespace Neuron\YourComponent\Commands;

use Neuron\Cli\Commands\Command;

class MakeControllerCommand extends Command
{
    /**
     * Get the command name (how it's invoked)
     */
    public function getName(): string
    {
        return 'make:controller';
    }
    
    /**
     * Get the command description (shown in list)
     */
    public function getDescription(): string
    {
        return 'Create a new controller class';
    }
    
    /**
     * Configure arguments and options
     */
    public function configure(): void
    {
        // Add required argument
        $this->addArgument('name', true, 'The controller name');
        
        // Add optional argument with default
        $this->addArgument('namespace', false, 'Custom namespace', 'App\\Controllers');
        
        // Add boolean option (flag)
        $this->addOption('resource', 'r', false, 'Create a resource controller');
        
        // Add option that accepts a value
        $this->addOption('model', 'm', true, 'Model to bind to controller');
        
        // Add option with default value
        $this->addOption('template', 't', true, 'Template to use', 'default');
    }
    
    /**
     * Execute the command
     * 
     * @return int Exit code (0 for success)
     */
    public function execute(): int
    {
        // Get arguments
        $name = $this->input->getArgument('name');
        $namespace = $this->input->getArgument('namespace');
        
        // Get options
        $isResource = $this->input->getOption('resource');
        $model = $this->input->getOption('model');
        
        // Output messages
        $this->output->info("Creating controller: {$name}");
        
        // Show progress for long operations
        $progress = $this->output->createProgressBar(100);
        $progress->start();
        
        for ($i = 0; $i < 100; $i++) {
            // Do work...
            $progress->advance();
            usleep(10000);
        }
        
        $progress->finish();
        
        // Success message
        $this->output->success("Controller created successfully!");
        
        return 0; // Success
    }
}

Command Naming Conventions

  • Use namespace format: component:action (e.g., mvc:controller, cms:init)
  • Use kebab-case for multi-word actions: make:controller, cache:clear
  • Group related commands under the same namespace
  • Keep names short but descriptive

Built-in Commands

Core Commands

Command Description Usage
help Display help for a command neuron help [command]
list List all available commands neuron list
version Show version information neuron version [--verbose]
config:env Export neuron.yaml as KEY=VALUE pairs `neuron config:env [--config=...] [--category=...] [--format=dotenv

Output Helpers

The Output class provides rich formatting options:

Basic Output

// Simple messages
$this->output->write('Simple message');
$this->output->writeln('Message with newline', 'green');

// Styled messages
$this->output->info('Information message');      // Cyan
$this->output->success('Success message');       // Green with ✓
$this->output->warning('Warning message');       // Yellow with ⚠
$this->output->error('Error message');          // Red with ✗
$this->output->comment('Comment');              // Yellow

// Sections and titles
$this->output->title('Command Title');
$this->output->section('Section Header');

Tables

$this->output->table(
    ['Name', 'Version', 'Description'],
    [
        ['cli', '1.0.0', 'CLI component'],
        ['mvc', '0.6.0', 'MVC framework'],
        ['cms', '0.5.0', 'Content management'],
    ]
);

Progress Bars

$progress = $this->output->createProgressBar(100);
$progress->start();

foreach ($items as $item) {
    // Process item...
    $progress->advance();
}

$progress->finish();

Interactive Input

The CLI component provides full support for interactive user input in commands:

// Check if terminal is interactive
if (!$this->input->isInteractive()) {
    $this->output->error('This command requires an interactive terminal');
    return 1;
}

// Ask for text input with optional default
$name = $this->input->ask('What is your name?', 'Anonymous');

// Ask without default (returns empty string if no input)
$email = $this->input->ask('Enter your email');

// Ask yes/no questions
if ($this->input->confirm('Do you want to continue?', true)) {
    // User confirmed (pressed y/yes/1/true or Enter with default true)
}

// Read raw input with custom prompt
$line = $this->input->readLine('> ');

Interactive Input Methods

Method Description Example
isInteractive() Check if terminal supports interaction if ($this->input->isInteractive())
ask($question, $default) Ask for text input $name = $this->input->ask('Name?', 'John')
confirm($question, $default) Ask yes/no question if ($this->input->confirm('Continue?', true))
askSecret($question) Ask for hidden input (passwords) $pass = $this->input->askSecret('Password')
choice($question, $choices, $default, $multiple) Select from options $opt = $this->input->choice('Pick:', ['A', 'B'])
readLine($prompt) Read raw input line $line = $this->input->readLine('> ')

Example: Interactive Setup Command

class SetupCommand extends Command
{
    public function execute(): int
    {
        if (!$this->input->isInteractive()) {
            $this->output->error('Setup requires interactive mode');
            return 1;
        }
        
        // Gather information
        $project = $this->input->ask('Project name', 'my-app');
        $author = $this->input->ask('Author name');
        $email = $this->input->ask('Author email');
        
        // Validate email
        while (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->output->error('Invalid email format');
            $email = $this->input->ask('Author email');
        }
        
        // Show summary
        $this->output->section('Configuration Summary');
        $this->output->write("Project: {$project}");
        $this->output->write("Author: {$author} <{$email}>");
        
        // Confirm
        if (!$this->input->confirm('Create project with these settings?', true)) {
            $this->output->warning('Setup cancelled');
            return 1;
        }
        
        // Proceed with setup...
        $this->output->success('Project created successfully!');
        return 0;
    }
}

Secret Input (Passwords)

The askSecret() method hides user input, perfect for passwords and sensitive data:

// Ask for password (input will be hidden)
$password = $this->input->askSecret('Enter password');

// Confirm password
$confirm = $this->input->askSecret('Confirm password');

if ($password !== $confirm) {
    $this->output->error('Passwords do not match');
    return 1;
}

Choice Selection

The choice() method presents options for selection:

// Simple choice from array
$colors = ['Red', 'Green', 'Blue'];
$color = $this->input->choice('Pick a color:', $colors, 'Blue');

// Associative array (key => display value)
$environments = [
    'dev' => 'Development',
    'staging' => 'Staging', 
    'prod' => 'Production'
];
$env = $this->input->choice('Select environment:', $environments, 'dev');

// Multiple selection
$features = [
    'api' => 'REST API',
    'auth' => 'Authentication',
    'cache' => 'Caching'
];
$selected = $this->input->choice(
    'Select features to enable:',
    $features,
    null,        // No default
    true         // Allow multiple
);
// Returns array like: ['api', 'auth']

// Users can select by:
// - Number: Type "1" for first option
// - Key: Type "dev" for development
// - Value: Type "Development" (case-insensitive)
// - Multiple: "1,3" or "api,cache" (when multiple allowed)

How Command Discovery Works

  1. Installation Detection: When neuron is run, the ComponentLoader scans for installed packages
  2. Package Filtering: Only neuron-php/* packages are considered
  3. Configuration Check: Each package's composer.json is checked for CLI configuration
  4. Provider Loading: If a CLI provider is found, its register() method is called
  5. Direct Registration: Any directly listed commands are registered
  6. Project Commands: The root project's composer.json is also checked

This automatic discovery means:

  • No manual registration needed
  • Commands available immediately after component installation
  • Clean separation between components
  • No conflicts between component commands

Error Handling

Commands should return appropriate exit codes:

  • 0 - Success
  • 1 - General error
  • 2 - Misuse of command
  • 126 - Command cannot execute
  • 127 - Command not found

Example:

public function execute(): int
{
    try {
        // Command logic...
        return 0;
    } catch (ValidationException $e) {
        $this->output->error('Validation failed: ' . $e->getMessage());
        return 2;
    } catch (\Exception $e) {
        $this->output->error('An error occurred: ' . $e->getMessage());
        return 1;
    }
}

Testing Your Commands

The CLI component provides comprehensive testing support, including the ability to test commands that require user input.

Testing Commands Without User Input

use PHPUnit\Framework\TestCase;
use Neuron\Cli\Console\Input;
use Neuron\Cli\Console\Output;

class MakeControllerCommandTest extends TestCase
{
    public function testExecute(): void
    {
        $command = new MakeControllerCommand();

        // Mock input
        $input = new Input(['UserController', '--resource']);
        $output = new Output(false); // No colors for testing

        $command->setInput($input);
        $command->setOutput($output);
        $command->configure();
        $input->parse($command);

        $exitCode = $command->execute();

        $this->assertEquals(0, $exitCode);
    }
}

Testing Commands With User Input

Commands that use prompt(), confirm(), secret(), or choice() can be tested using the TestInputReader:

use PHPUnit\Framework\TestCase;
use Neuron\Cli\Console\Input;
use Neuron\Cli\Console\Output;
use Neuron\Cli\IO\TestInputReader;

class SetupCommandTest extends TestCase
{
    public function testInteractiveSetup(): void
    {
        $command = new SetupCommand();

        // Create test input reader with pre-programmed responses
        $inputReader = new TestInputReader();
        $inputReader->addResponse('my-project');        // Project name
        $inputReader->addResponse('John Doe');          // Author name
        $inputReader->addResponse('john@example.com');  // Email
        $inputReader->addResponse('yes');               // Confirmation

        // Configure command
        $input = new Input([]);
        $output = new Output(false);

        $command->setInput($input);
        $command->setOutput($output);
        $command->setInputReader($inputReader);

        // Execute command
        $exitCode = $command->execute();

        // Assertions
        $this->assertEquals(0, $exitCode);

        // Verify the prompts that were shown
        $prompts = $inputReader->getPromptHistory();
        $this->assertCount(4, $prompts);
        $this->assertStringContainsString('Project name', $prompts[0]);
        $this->assertStringContainsString('Author name', $prompts[1]);
    }

    public function testUserCancelsSetup(): void
    {
        $command = new SetupCommand();

        // User will cancel the setup
        $inputReader = new TestInputReader();
        $inputReader->addResponses([
            'test-project',
            'Test User',
            'test@example.com',
            'no'  // Cancel confirmation
        ]);

        $input = new Input([]);
        $output = new Output(false);

        $command->setInput($input);
        $command->setOutput($output);
        $command->setInputReader($inputReader);

        $exitCode = $command->execute();

        // Should return non-zero exit code when cancelled
        $this->assertNotEquals(0, $exitCode);
    }
}

Using Input Reader in Commands

To make your commands testable, use the convenience methods provided by the Command base class:

class SetupCommand extends Command
{
    public function execute(): int
    {
        // Use built-in convenience methods instead of reading STDIN directly
        $name = $this->prompt('Enter project name: ');

        if ($this->confirm('Enable caching?', true)) {
            // User confirmed
        }

        $password = $this->secret('Enter password: ');

        $env = $this->choice(
            'Select environment:',
            ['development', 'staging', 'production'],
            'development'
        );

        return 0;
    }
}

These convenience methods automatically use the injected IInputReader, making your commands fully testable without requiring actual user input.

TestInputReader Features

The TestInputReader class provides:

  • Response Queue: Pre-program multiple responses with addResponse() or addResponses()
  • Prompt History: Track all prompts shown with getPromptHistory()
  • Automatic Validation: Throws exceptions if responses run out, helping catch test bugs
  • Fluent Interface: Chain addResponse() calls for cleaner test setup
  • Full Interface Support: Implements all IInputReader methods (prompt, confirm, secret, choice)
$reader = new TestInputReader();
$reader
    ->addResponse('value1')
    ->addResponse('value2')
    ->addResponse('yes');

// Check if there are responses remaining
if ($reader->hasMoreResponses()) {
    $count = $reader->getRemainingResponseCount();
}

// Get history of prompts
$prompts = $reader->getPromptHistory();

// Reset for reuse
$reader->reset();

Contributing

When adding new features to the CLI component:

  1. Extend the Command base class for new commands
  2. Add tests in the tests/ directory
  3. Update this README with new features
  4. Follow PSR-4 naming conventions
  5. Use the existing code style (tabs, spaces etc)

License

This component is part of the Neuron PHP Framework and is released under the MIT License.

统计信息

  • 总下载量: 1.02k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 4
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-08-13