mnapoli/silly
Silly is a lightweight CLI micro-framework built on Symfony Console. Define commands with simple signatures and PHP callables, get options/arguments parsing, helpers, and DI integration (PHP-DI or Pimple) while staying compatible with Symfony Console apps.
Installation:
composer require mnapoli/silly
For PHP-DI integration (recommended for Laravel projects):
composer require mnapoli/silly-php-di
Basic CLI Setup:
Create a file (e.g., artisan.php) with:
use Silly\Application;
$app = new Application();
$app->command('greet [name]', function ($name) {
echo "Hello, {$name ?: 'World'}!\n";
});
$app->run();
First Use Case: Run the command:
php artisan.php greet John
Output: Hello, John!
Service Provider Setup:
Register Silly in AppServiceProvider:
use Silly\Application;
use Silly\Bridge\Laravel\SillyServiceProvider;
public function register()
{
$this->app->singleton('silly', function ($app) {
$silly = new Application();
$silly->useContainer($app); // Laravel's container is PSR-11 compliant
return $silly;
});
}
Command Registration:
Define commands in a dedicated class (e.g., app/Console/SillyCommands.php):
$silly = app('silly');
$silly->command('user:create [name]', function ($name, \Psr\Log\LoggerInterface $logger) {
$logger->info("Creating user: {$name}");
// Logic here
});
Artisan Integration (Optional): Extend Laravel’s Artisan to delegate to Silly:
use Illuminate\Console\Scheduling\Schedule;
use Silly\Application;
protected function schedule(Schedule $schedule)
{
$silly = new Application();
$silly->command('schedule:run', function () {
// Silly command logic
});
$silly->run();
}
Dependency Injection: Leverage Laravel’s container for services:
$silly->command('db:backup', function (\Illuminate\Filesystem\Filesystem $filesystem) {
$filesystem->ensureDirectoryExists(storage_path('backups'));
});
Closure-Based Commands: Use for simple, one-off tasks:
$silly->command('cache:clear', function () {
Artisan::call('cache:clear');
});
Class-Based Commands: For reusable logic with DI:
$silly->command('migrate', [App\Console\MigrateCommand::class, 'handle']);
SymfonyStyle Integration: Enhance output with styled prompts:
$silly->command('deploy', function (\Symfony\Component\Console\Style\SymfonyStyle $io) {
$io->title('Deploying...');
$io->section('Steps');
$io->list(['Step 1', 'Step 2']);
});
Subcommands: Organize commands hierarchically:
$silly->command('user:list', function () { /* ... */ });
$silly->command('user:create', function () { /* ... */ });
Parameter Order Sensitivity: Silly matches parameters by name, not position. Mixing command arguments with DI parameters may cause unexpected behavior if names collide. Fix: Use explicit type-hints for DI parameters:
$silly->command('user:create [name]', function (UserRepository $users, $name) {
$users->create($name);
});
Container Binding Conflicts: If using Laravel’s container, ensure Silly’s container isn’t overridden: Fix: Register Silly’s container after Laravel’s bindings:
$silly->useContainer(app(), true, true); // Enable type-hint and name injection
Hyphen to CamelCase Conversion:
Options like --dry-run become $dryRun in the closure. Forgetting this causes Undefined variable errors.
Fix: Use snake_case in closures if preferred:
$silly->command('run [--dry-run]', function ($dry_run) { /* ... */ });
Default Values Override:
Explicit defaults in ->defaults() override closure defaults:
$silly->command('greet [name]', function ($name = 'Guest') { /* ... */ })
->defaults(['name' => 'User']);
Result: $name will always be 'User'.
PHP-DI Autowiring:
If using silly-php-di, ensure your classes are autoloaded and follow PSR-4 conventions.
var_dump($input->getArguments()) or var_dump($input->getOptions()) to debug command parsing.--verbose to Silly commands to see execution flow:
php artisan.php --verbose user:create John
if (!app()->has(UserRepository::class)) {
throw new \RuntimeException('UserRepository not bound!');
}
Custom Command Helpers:
Extend Silly’s Application to add reusable methods:
class CustomApplication extends Application
{
public function commandWithLogging($name, $callback)
{
return $this->command($name, function ($input, $output, $callback) {
$output->writeln('<info>Executing...</info>');
$callback($input, $output);
});
}
}
Middleware for Commands: Use closures to wrap commands (e.g., for auth):
$silly->command('admin:task', function ($input, $output) {
if (!auth()->check()) {
$output->writeln('Unauthorized!');
return;
}
// Proceed with command
});
Event Listeners: Attach listeners to command execution:
$silly->on('command.test', function ($event) {
Log::info('Command "test" started', ['input' => $event->getInput()]);
});
Laravel Mix Integration:
Use Silly for build scripts in webpack.mix.js:
mix.silly('build', function () {
mix.js('resources/js/app.js', 'public/js');
});
Application instance:
$silly = app('silly'); // Reuse instance
Artisan Command Aliases: Register Silly commands as Artisan commands for consistency:
Artisan::add(new class extends Command {
protected $signature = 'silly:greet {name?}';
public function handle() {
$silly = app('silly');
$silly->run(['greet', $this->argument('name')]);
}
});
Service Container Binding: Bind Silly’s container to Laravel’s container for global access:
$this->app->instance('silly', $silly);
Testing:
Use Laravel’s Artisan::call() to test Silly commands:
$this->artisan('silly:greet John')
->expectsOutput('Hello, John!')
->assertExitCode(0);
How can I help you explore Laravel packages today?