A unified command-line interface for the Neuron PHP framework that provides a modern, extensible CLI tool for all Neuron components.
- 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
- ️ Built on Solid Foundation - Extends Neuron's CommandLineBase for consistency
- PHP 8.4 or higher
- Composer
- Neuron Application component
composer require neuron-php/cli
After installation, the CLI will be available at:
./vendor/bin/neuron
composer global require neuron-php/cli
Make sure your global Composer bin directory is in your PATH, then:
neuron version
neuron list
# General help
neuron --help
# Help for specific command
neuron help make:controller
# Run a command
neuron make:controller UserController
# With options
neuron make:controller UserController --resource
# With verbose output
neuron make:controller UserController -v
The CLI component provides two ways for your component to register commands:
This is the preferred method as it keeps all command registrations in one place.
- Update your component's
composer.json
:
{
"name": "neuron-php/your-component",
"extra": {
"neuron": {
"cli-provider": "Neuron\\YourComponent\\Cli\\CommandProvider"
}
}
}
- 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);
}
}
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"
}
}
}
}
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
}
}
- 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
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] |
The Output
class provides rich formatting options:
// 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');
$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 = $this->output->createProgressBar(100);
$progress->start();
foreach ($items as $item) {
// Process item...
$progress->advance();
}
$progress->finish();
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('> ');
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('> ') |
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;
}
}
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;
}
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)
- Installation Detection: When
neuron
is run, the ComponentLoader scans for installed packages - Package Filtering: Only
neuron-php/*
packages are considered - Configuration Check: Each package's
composer.json
is checked for CLI configuration - Provider Loading: If a CLI provider is found, its
register()
method is called - Direct Registration: Any directly listed commands are registered
- 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
class RoutesListCommand extends Command
{
public function getName(): string
{
return 'routes:list';
}
public function getDescription(): string
{
return 'Display all registered routes';
}
public function execute(): int
{
$routes = $this->getRoutes(); // Your logic here
$this->output->table(
['Method', 'Path', 'Controller', 'Action'],
$routes
);
return 0;
}
}
class InitCommand extends Command
{
public function getName(): string
{
return 'cms:init';
}
public function getDescription(): string
{
return 'Initialize CMS structure';
}
public function configure(): void
{
$this->addOption('theme', null, true, 'Initial theme', 'default');
$this->addOption('with-sample', null, false, 'Include sample content');
}
public function execute(): int
{
$theme = $this->input->getOption('theme');
$this->output->title('CMS Initialization');
// Create directories
$this->output->info('Creating directory structure...');
// ... implementation
// Install theme
$this->output->info("Installing theme: {$theme}");
// ... implementation
if ($this->input->getOption('with-sample')) {
$this->output->info('Adding sample content...');
// ... implementation
}
$this->output->success('CMS initialized successfully!');
return 0;
}
}
Commands should return appropriate exit codes:
0
- Success1
- General error2
- Misuse of command126
- Command cannot execute127
- 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;
}
}
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);
}
}
When adding new features to the CLI component:
- Extend the
Command
base class for new commands - Add tests in the
tests/
directory - Update this README with new features
- Follow PSR-4 naming conventions
- Use the existing code style (tabs, spaces etc)
This component is part of the Neuron PHP Framework and is released under the MIT License.