marwen-brini / yalla
A standalone PHP CLI framework built from scratch
Installs: 7
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 6
pkg:composer/marwen-brini/yalla
Requires
- php: ^8.1|^8.2|^8.3|^8.4
Requires (Dev)
- laravel/pint: ^1.0
- mockery/mockery: ^1.6
- pestphp/pest: ^2.0|^3.0
- phpstan/phpstan: ^1.10|^2.0
- phpstan/phpstan-phpunit: ^1.3|^2.0
- spatie/ray: ^1.28
- dev-main
- 2.0.0
- 1.5.0
- 1.4.0
- 1.3.0
- dev-dependabot/github_actions/stefanzweifel/git-auto-commit-action-7
- dev-feat/migration-system
- dev-feat/progress-indicators
- dev-feat/interactive-confirmation-system
- dev-feat/custom-table-output
- dev-dependabot/github_actions/actions/upload-pages-artifact-4
- dev-dependabot/github_actions/actions/configure-pages-5
- dev-dependabot/github_actions/actions/checkout-5
- dev-dependabot/github_actions/codecov/codecov-action-5
This package is auto-updated.
Last update: 2025-12-13 05:57:51 UTC
README
A standalone PHP CLI framework built from scratch without dependencies.
Features
- Zero Dependencies: Built entirely from scratch without relying on Symfony Console or other frameworks
- Interactive REPL: Full-featured Read-Eval-Print-Loop for interactive PHP development
- Command Routing: Custom command parser and router
- Colored Output: ANSI color support for beautiful terminal output (cross-platform)
- Advanced Table Rendering: Professional table formatter with multiple border styles, emoji support, and alignment options
- Migration Tables: Specialized table formatter for database migration systems
- Progress Indicators: Progress bars, spinners, and step indicators for long-running tasks
- Async Command Execution (v2.0): Run commands asynchronously with promises and parallel execution
- Signal Handling (v2.0): Graceful shutdown and cleanup on interrupt signals (Unix/Linux)
- Command Middleware (v2.0): Authentication, logging, timing, and custom middleware pipeline
- Dry Run Mode (v2.0): Preview command operations without executing them
- Environment Management (v2.0): .env file support with variable expansion
- File System Helpers (v2.0): Safe file operations, backup, unique filenames, and directory utilities
- Stub Generator (v2.0): Template-based code generation with conditionals and loops
- Process Locking (v2.0): Prevent concurrent command execution with file-based locks
- Command Aliases (v2.0): Create shortcuts for frequently used commands
- Exit Codes (v2.0): Standard exit codes with descriptions and exception mapping
- Command Signatures (v2.0): Laravel-style signature parsing for arguments and options
- Enhanced Output (v2.0): Output sections, semantic methods, verbosity levels, and timestamped logs
- Input Parsing: Handles commands, arguments, and options (long and short formats)
- Command Scaffolding: Built-in
create:commandto generate new command boilerplate - History & Autocomplete: REPL with command history and intelligent autocompletion
- Extensible Architecture: Plugin system for custom REPL extensions
- 100% Test Coverage: Fully tested with Pest PHP
- Cross-Platform: Works on Windows, macOS, and Linux
Requirements
- PHP 8.1, 8.2, 8.3, or 8.4
- Composer 2.0+
Installation
composer require marwen-brini/yalla
Usage
Basic Usage
Create a CLI script (e.g., bin/yalla):
#!/usr/bin/env php <?php require 'vendor/autoload.php'; use Yalla\Application; $app = new Application('Yalla CLI', '2.0.0'); $app->run();
Make it executable:
chmod +x bin/yalla
Creating Custom Commands
<?php use Yalla\Commands\Command; use Yalla\Output\Output; class GreetCommand extends Command { public function __construct() { $this->name = 'greet'; $this->description = 'Greet someone'; $this->addArgument('name', 'The name to greet', true); $this->addOption('yell', 'y', 'Yell the greeting', false); } public function execute(array $input, Output $output): int { $name = $this->getArgument($input, 'name'); $message = "Hello, $name!"; if ($this->getOption($input, 'yell')) { $message = strtoupper($message); } $output->success($message); return 0; } } // Register in your application $app = new Application('Yalla CLI', '2.0.0'); $app->register(new GreetCommand()); $app->run();
Running Commands
# List all commands ./bin/yalla list # Get help for a command ./bin/yalla help greet # Run a command ./bin/yalla greet World ./bin/yalla greet World --yell ./bin/yalla greet World -y
Command Scaffolding
Yalla includes a built-in command generator to quickly create new commands:
# Create a new command with default settings ./bin/yalla create:command deploy # Create with custom class name ./bin/yalla create:command deploy --class=DeployApplicationCommand # Create in a custom directory ./bin/yalla create:command deploy --dir=src/Commands/Deployment # Force overwrite if file exists ./bin/yalla create:command deploy --force
This will generate a command class with the proper structure:
<?php declare(strict_types=1); namespace Yalla\Commands; use Yalla\Commands\Command; use Yalla\Output\Output; class DeployCommand extends Command { public function __construct() { $this->name = 'deploy'; $this->description = 'Description of your command'; // Define arguments and options here } public function execute(array $input, Output $output): int { $output->info('Executing deploy command...'); // Your command logic here $output->success('deploy completed successfully!'); return 0; } }
Interactive REPL
Yalla includes a powerful REPL (Read-Eval-Print-Loop) for interactive PHP development:
Starting the REPL
# Start the REPL ./bin/yalla repl # With custom configuration ./bin/yalla repl --config=repl.config.php # Disable colors ./bin/yalla repl --no-colors # Disable history ./bin/yalla repl --no-history
REPL Commands
:help- Show available commands:exit- Exit the REPL:vars- Show defined variables:imports- Show imported classes:clear- Clear the screen:history- Show command history:mode [mode]- Switch display mode (compact, verbose, json, dump)
REPL Features
- Command History: Navigate through previous commands with up/down arrows
- Variable Persistence: Variables defined in the REPL persist across commands
- Shortcuts: Define shortcuts for frequently used classes
- Auto-imports: Automatically import specified classes
- Custom Extensions: Add your own commands and functionality
- Multiple Display Modes: Choose between compact, verbose, JSON, or dump output formats
- Smart Object Display: Enhanced display for objects with public properties or
__toString()methods - Semicolon Support: Natural PHP syntax with trailing semicolons
- ORM-Friendly: Properly handles objects with protected/private properties
Display Modes
The REPL supports multiple display modes for different use cases:
# Compact mode (default) - Clean, colorized output [1] > ['id' => 1, 'name' => 'Alice'] ['id' => 1, 'name' => "Alice"] # Verbose mode - Detailed object/array information [2] > :mode verbose [3] > $user ═══ Object Details ═══ Class: User Properties: public $id = 1 public $name = "Alice" Public Methods: - save($params) - delete() # JSON mode - Perfect for API data [4] > :mode json [5] > ['status' => 'success', 'data' => ['id' => 1]] { "status": "success", "data": {"id": 1} } # Dump mode - Traditional PHP debugging [6] > :mode dump [7] > "test" string(4) "test"
REPL Configuration
Create a repl.config.php file:
<?php return [ 'shortcuts' => [ 'User' => '\App\Models\User', 'DB' => '\Illuminate\Support\Facades\DB', ], 'imports' => [ ['class' => '\Carbon\Carbon', 'alias' => 'Carbon'], ], 'display' => [ 'prompt' => 'myapp> ', 'performance' => true, // Show execution time and memory 'mode' => 'compact', // Options: compact, verbose, json, dump ], 'history' => [ 'file' => $_ENV['HOME'] . '/.myapp_history', 'max_entries' => 1000, ], ];
Creating REPL Extensions
use Yalla\Repl\ReplExtension; use Yalla\Repl\ReplContext; class MyExtension implements ReplExtension { public function register(ReplContext $context): void { // Add custom commands $context->addCommand('models', function($args, $output) { // List all models }); // Add shortcuts $context->addShortcut('User', '\App\Models\User'); // Add custom formatters $context->addFormatter('MyClass', function($value, $output) { $output->info('Custom formatting for MyClass'); }); } public function boot(): void { // Bootstrap your extension } public function getName(): string { return 'My Custom Extension'; } public function getVersion(): string { return '1.4.0'; } public function getDescription(): string { return 'Adds custom functionality to the REPL'; } }
Creating Commands
Commands extend the base Command class:
use Yalla\Commands\Command; use Yalla\Output\Output; class MyCommand extends Command { public function __construct() { $this->name = 'my:command'; $this->description = 'Does something awesome'; // Add arguments $this->addArgument('input', 'Input file', true); $this->addArgument('output', 'Output file', false); // Add options $this->addOption('force', 'f', 'Force overwrite', false); $this->addOption('verbose', 'v', 'Verbose output', false); } public function execute(array $input, Output $output): int { // Get arguments $inputFile = $this->getArgument($input, 'input'); $outputFile = $this->getArgument($input, 'output', 'output.txt'); // Get options $force = $this->getOption($input, 'force', false); $verbose = $this->getOption($input, 'verbose', false); // Use output methods $output->info('Processing...'); $output->success('Done!'); $output->error('Something went wrong!'); $output->warning('Be careful!'); // Draw tables with enhanced formatting $output->table( ['Name', 'Age', 'City', 'Status'], [ ['John', '30', 'New York', '✅ Active'], ['Jane', '25', 'London', '⏳ Pending'], ['Bob', '35', 'Paris', '❌ Inactive'], ], [ 'borders' => 'unicode', 'alignment' => ['left', 'center', 'left', 'center'] ] ); return 0; // Success } }
Output Formatting
The Output class provides various formatting methods:
// Basic output $output->write('Hello'); // No newline $output->writeln('Hello'); // With newline // Colored output $output->success('Success!'); // Green $output->error('Error!'); // Red $output->warning('Warning!'); // Yellow $output->info('Info'); // Cyan // Custom colors $output->writeln($output->color('Custom', Output::MAGENTA)); // Basic tables $output->table(['Header 1', 'Header 2'], $rows);
Advanced Table Formatting (New in v1.4)
Yalla 1.4 introduces a powerful table formatter with multiple border styles, emoji support, and advanced formatting options:
use Yalla\Output\Table; // Create table with options $table = $output->createTable([ 'borders' => Table::BORDER_UNICODE, // unicode, ascii, markdown, etc. 'alignment' => [Table::ALIGN_LEFT, Table::ALIGN_CENTER, Table::ALIGN_RIGHT], 'colors' => true, 'max_width' => 120 ]); $table->setHeaders(['Migration', 'Batch', 'Status', 'Date']) ->addRow(['2024_01_create_users', '1', '✅ Migrated', '2024-01-15']) ->addRow(['2024_02_create_posts', '2', '⏳ Pending', null]) ->addRow(['2024_03_add_indexes', '3', '❌ Error', null]) ->render();
Border Styles
// Available border styles Table::BORDER_UNICODE // ┌─┬─┐ (default) Table::BORDER_ASCII // +---+ Table::BORDER_MARKDOWN // | --- | Table::BORDER_DOUBLE // ╔═╦═╗ Table::BORDER_ROUNDED // ╭─┬─╮ Table::BORDER_COMPACT // minimal Table::BORDER_NONE // no borders
Advanced Features
// Column alignment $table->setOptions(['alignment' => [ Table::ALIGN_LEFT, // Left align Table::ALIGN_CENTER, // Center align Table::ALIGN_RIGHT // Right align ]]); // Cell formatters $table->setCellFormatter(2, function($value) { return is_numeric($value) ? number_format($value) : $value; }); // Sorting and filtering $table->sortBy(1, 'desc') // Sort by column 1 descending ->filter(fn($row) => $row[2] === 'Active'); // Filter rows // Row indices $table->setOptions(['show_index' => true, 'index_name' => 'ID']);
Migration Tables
For database migration systems, use the specialized MigrationTable:
use Yalla\Output\MigrationTable; $migrationTable = new MigrationTable($output); $migrationTable->addMigration('2024_01_create_users', 1, 'migrated', '2024-01-15') ->addMigration('2024_02_create_posts', 2, 'pending', null) ->addMigration('2024_03_add_indexes', null, 'error: Constraint violation', null) ->render(); // Render summary $migrationTable->renderSummary();
Progress Indicators (New in v1.5)
Yalla provides powerful progress indicators for long-running tasks:
Progress Bars
use Yalla\Output\Output; $output = new Output(); $progress = $output->createProgressBar(100); $progress->start(); for ($i = 0; $i < 100; $i++) { // Do work... $progress->advance(); } $progress->finish();
Multiple formats available:
- normal: Basic progress bar with percentage
- verbose: Includes custom messages
- detailed: Shows time estimates
- minimal: Compact display
- memory: Displays memory usage
Spinners
$spinner = $output->createSpinner('Processing...', 'dots'); $spinner->start(); // Do work... for ($i = 0; $i < 30; $i++) { usleep(100000); $spinner->advance(); } $spinner->success('Process complete!'); // Also available: ->error(), ->warning(), ->info()
Frame styles: dots, line, pipe, arrow, bounce, box
Step Indicators
$steps = $output->steps([ 'Download files', 'Install dependencies', 'Run migrations', 'Clear cache' ]); $steps->start(); // Mark steps as you complete them $steps->complete(0, 'Files downloaded'); $steps->complete(1, 'Dependencies installed'); $steps->skip(2, 'Migrations skipped'); $steps->fail(3, 'Cache clear failed'); $steps->finish();
New in v2.0: Advanced Features
Async Command Execution
Run commands asynchronously with promises and parallel execution:
use Yalla\Commands\Traits\SupportsAsync; class ProcessCommand extends Command { use SupportsAsync; protected bool $runAsync = true; // Enable async by default public function execute(array $input, Output $output): int { // Long running task... $output->info('Processing data...'); sleep(5); return 0; } } // Run multiple commands in parallel $command->runParallel([ fn() => $this->processFile('file1.txt'), fn() => $this->processFile('file2.txt'), fn() => $this->processFile('file3.txt'), ], $output);
Signal Handling
Gracefully handle interrupt signals (Unix/Linux):
use Yalla\Commands\Traits\HandlesSignals; class LongRunningCommand extends Command { use HandlesSignals; public function execute(array $input, Output $output): int { // Register signal handlers $this->onInterrupt(function() use ($output) { $output->warning('Received interrupt signal, cleaning up...'); $this->cleanup(); exit(130); }); $this->onTerminate(function() use ($output) { $output->warning('Received terminate signal...'); $this->cleanup(); exit(143); }); // Process work... while ($this->hasWork()) { $this->dispatchSignals(); $this->processNextItem(); } return 0; } }
Command Middleware
Add authentication, logging, timing, and custom middleware:
use Yalla\Commands\Traits\HasMiddleware; use Yalla\Commands\Middleware\TimingMiddleware; use Yalla\Commands\Middleware\LoggingMiddleware; class DeployCommand extends Command { use HasMiddleware; public function __construct() { parent::__construct(); $this->middleware(new TimingMiddleware()) ->middleware(new LoggingMiddleware()) ->middleware(new AuthenticationMiddleware()); } } // Create custom middleware class AuthenticationMiddleware implements MiddlewareInterface { public function handle(Command $command, array $input, Output $output, callable $next): int { if (!$this->isAuthenticated()) { $output->error('Authentication required'); return 1; } return $next($command, $input, $output); } public function getPriority(): int { return 100; // Higher priority runs first } }
Dry Run Mode
Preview operations without executing them:
use Yalla\Commands\Traits\DryRunnable; class MigrationCommand extends Command { use DryRunnable; public function execute(array $input, Output $output): int { $this->setDryRun($this->getOption($input, 'dry-run', false)); $this->setDryRunOutput($output); $this->executeOrSimulate( 'Create users table', fn() => $this->createUsersTable(), ['table' => 'users', 'columns' => 5] ); $this->executeOrSimulate( 'Add indexes', fn() => $this->addIndexes() ); if ($this->isDryRun()) { $this->showDryRunSummary(); } return 0; } } // Run with --dry-run flag // ./bin/yalla migrate --dry-run
Environment Management
Load and manage environment variables with .env support:
use Yalla\Environment\Environment; $env = new Environment(['.env', '.env.local']); // Get values with type casting $dbHost = $env->get('DB_HOST', 'localhost'); $dbPort = $env->getInt('DB_PORT', 3306); $debug = $env->getBool('APP_DEBUG', false); $allowedHosts = $env->getArray('ALLOWED_HOSTS'); // Comma-separated // Environment detection if ($env->isProduction()) { // Production logic } if ($env->isDebug()) { $output->info('Debug mode enabled'); } // Variable expansion // APP_URL=https://example.com // API_URL=${APP_URL}/api $apiUrl = $env->get('API_URL'); // https://example.com/api
File System Helpers
Safe file operations with backup and atomic writes:
use Yalla\Filesystem\FileHelper; $helper = new FileHelper(); // Safe write with automatic backup $helper->safeWrite('/path/to/config.php', $content, backup: true); // Generate unique filenames $logFile = $helper->uniqueFilename('/logs', 'app-{date}-{counter}.log'); // Result: /logs/app-2025-10-01-1.log // Copy directories recursively $helper->copyDirectory('/source', '/destination', overwrite: false); // Find files with pattern $phpFiles = $helper->findFiles('/src', '*.php', recursive: true); // Human-readable file sizes echo $helper->humanFilesize('/large/file.zip'); // "1.5 GB" // Relative paths $rel = $helper->relativePath('/var/www/app', '/var/www/app/public'); // Result: "public"
Stub Generator
Template-based code generation with conditionals and loops:
use Yalla\Filesystem\StubGenerator; $generator = new StubGenerator(); $generator->registerStubDirectory(__DIR__ . '/stubs'); // Generate from template $generator->generate('model', '/app/Models/User.php', [ 'namespace' => 'App\\Models', 'class' => 'User', 'table' => 'users', 'fillable' => ['name', 'email', 'password'], 'timestamps' => true, 'softDeletes' => false, ]); // Template: model.stub // namespace {{ namespace }}; // // class {{ class }} // { // @if(timestamps) // protected $timestamps = true; // @endif // // protected $fillable = [ // @each(fillable as item) // '{{ item }}', // @endeach // ]; // }
Process Locking
Prevent concurrent command execution:
use Yalla\Process\LockManager; class CronCommand extends Command { public function execute(array $input, Output $output): int { $lockManager = new LockManager(); if (!$lockManager->acquire('cron-job', timeout: 60)) { $output->error('Another instance is running'); return 1; } try { // Do work... $this->processCronJobs(); // Keep lock alive during long operations $lockManager->refresh('cron-job'); } finally { $lockManager->release('cron-job'); } return 0; } } // Check lock status $status = $lockManager->getLockStatus('cron-job'); // Check if stale if ($lockManager->isStale('cron-job', maxAge: 3600)) { $lockManager->forceRelease('cron-job'); }
Command Aliases
Create shortcuts for frequently used commands:
$app = new Application('Yalla CLI', '2.0.0'); // Register command with aliases $migrateCommand = new MigrateCommand(); $migrateCommand->setAliases(['m', 'db:migrate']); $app->register($migrateCommand); // Or use fluent API $app->alias('deploy', 'd'); $app->alias('deploy', 'dep'); // All these work the same: // ./bin/yalla migrate // ./bin/yalla m // ./bin/yalla db:migrate
Exit Codes
Standard exit codes with descriptions and exception mapping:
use Yalla\Commands\ExitCodes; class BackupCommand extends Command implements ExitCodes { public function execute(array $input, Output $output): int { if (!$this->canConnect()) { return $this->returnWithCode( self::EXIT_UNAVAILABLE, 'Database unavailable', $output ); } try { $this->createBackup(); return $this->returnSuccess($output); } catch (\Exception $e) { return $this->handleException($e, $output, debug: true); } } } // Available exit codes: // EXIT_SUCCESS = 0 // EXIT_ERROR = 1 // EXIT_INVALID_ARGUMENT = 2 // EXIT_UNAVAILABLE = 69 // EXIT_PERMISSION_DENIED = 77 // EXIT_CONFIG_ERROR = 78
Command Signatures
Laravel-style signature parsing:
use Yalla\Commands\Traits\HasSignature; class DeployCommand extends Command { use HasSignature; protected string $signature = 'deploy {environment} {--force} {--tag=latest}'; public function execute(array $input, Output $output): int { $environment = $this->argument('environment'); $force = $this->option('force'); $tag = $this->option('tag'); $output->info("Deploying to $environment with tag $tag"); return 0; } }
Enhanced Output
Output sections, semantic methods, and verbosity levels:
// Output sections for dynamic updates $section1 = $output->section('Downloads'); $section2 = $output->section('Processing'); $section1->writeln('Downloading file 1...'); $section2->writeln('Processing data...'); // Update sections dynamically $section1->overwrite('Download complete!'); // Semantic output methods $output->success('✓ Operation completed'); $output->error('✗ Operation failed'); $output->warning('⚠ Warning message'); $output->info('ℹ Information'); // Verbosity levels $output->verbose('Detailed information', 'v'); $output->debug('Debug information', 'vvv'); // Timestamped output $output->withTimestamps(true); $output->info('This has a timestamp'); // SQL query logging $output->logQuery('SELECT * FROM users WHERE id = ?', [1], 0.023); // Grouped output $output->startGroup('Database Operations'); $output->info('Creating tables...'); $output->info('Adding indexes...'); $output->endGroup(); ## Architecture Yalla is built with a clean, modular architecture: - **Application**: Main entry point that handles command registration and execution - **CommandRegistry**: Manages command registration and retrieval - **InputParser**: Parses CLI arguments into structured data - **Output**: Handles all terminal output with color support - **Command**: Base class for all commands ## Testing Yalla maintains 100% code coverage with comprehensive test suite using Pest PHP: ```bash # Run tests composer test # Run tests with coverage (requires Xdebug or PCOV) composer test-coverage # Generate HTML coverage report composer test-coverage-html # Run specific test file vendor/bin/pest tests/ApplicationTest.php
Documentation
Full documentation is available at https://marwen-brini.github.io/Yalla/
Quick Links
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Quick Start
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
Marwen-Brini - Initial work - GitHub
Acknowledgments
- Thanks to all contributors who have helped shape Yalla CLI
- Inspired by the simplicity and power of modern CLI tools
- Built with love for the PHP community