Silly
A micro-framework for creating CLI applications in PHP. Based on Symfony Console, provides a simpler API.
Framework
Silly
Overview
Silly is a micro-framework for creating CLI applications in PHP. Based on Symfony Console, it provides a simpler API. It's chosen by developers who want to quickly create simple CLI tools and can be started with minimal configuration as a lightweight framework.
Details
Silly leverages the power of Symfony Console component while providing a more intuitive and concise API. It allows you to define commands with a functional approach without complex configuration, making it ideal for developing small CLI tools.
Key Features
- Simple API: Define commands with minimal code
- Functional Approach: Write command logic with closures or functions
- Symfony Console Foundation: Leverage stable Symfony Console features
- Lightweight: Minimal dependencies and overhead
- Flexibility: Direct access to Symfony Console features when needed
- PSR Compliant: Integration with PSR-11 containers
- CLI Display: Rich display features like progress bars, tables
Pros and Cons
Pros
- Low Learning Curve: Very simple and easy-to-understand API
- Rapid Development: Build CLI tools quickly with minimal code
- Lightweight: Few dependencies and low memory usage
- Symfony Compatible: Can utilize rich Symfony Console features
- Flexibility: Supports both functional and object-oriented approaches
Cons
- Feature Limitations: Not suitable for large, complex CLI applications
- Community: Smaller community compared to other major frameworks
- Extensibility: Limited advanced customization capabilities
- Documentation: Relatively fewer documents and resources
Key Links
Usage Examples
Installation and Basic Setup
composer require silly/silly
Basic Usage
#!/usr/bin/env php
<?php
// cli-app.php
require_once 'vendor/autoload.php';
use Silly\Application;
$app = new Application();
// Simple command definition
$app->command('hello [name]', function ($name, $output) {
$name = $name ?: 'World';
$output->writeln("Hello $name!");
});
// Command with options
$app->command('greet name [--yell]', function ($name, $yell, $output) {
$message = "Hello $name";
if ($yell) {
$message = strtoupper($message);
}
$output->writeln($message);
});
// Command with multiple arguments
$app->command('sum numbers*', function (array $numbers, $output) {
$sum = array_sum($numbers);
$output->writeln("Sum: $sum");
});
$app->run();
Advanced Example
#!/usr/bin/env php
<?php
// advanced-cli.php
require_once 'vendor/autoload.php';
use Silly\Application;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
$app = new Application('File Manager', '1.0.0');
// File processing command
$app->command('process:files input [--format=] [--output=]', function (
$input,
$format,
$output,
InputInterface $inputInterface,
OutputInterface $outputInterface
) {
$format = $format ?: 'txt';
$outputInterface->writeln("<info>Processing files from: $input</info>");
$outputInterface->writeln("<comment>Format: $format</comment>");
// Get file list
$files = glob("$input/*.$format");
if (empty($files)) {
$outputInterface->writeln("<error>No files found with format: $format</error>");
return 1;
}
// Show progress bar
$progressBar = new ProgressBar($outputInterface, count($files));
$progressBar->start();
$results = [];
foreach ($files as $file) {
$results[] = [
'file' => basename($file),
'size' => filesize($file),
'modified' => date('Y-m-d H:i:s', filemtime($file))
];
$progressBar->advance();
usleep(100000); // Simulate processing
}
$progressBar->finish();
$outputInterface->writeln('');
// Display results in table
$table = new Table($outputInterface);
$table->setHeaders(['File', 'Size (bytes)', 'Modified']);
$table->setRows($results);
$table->render();
// File output
if ($output) {
$csv = "File,Size,Modified\n";
foreach ($results as $row) {
$csv .= implode(',', $row) . "\n";
}
file_put_contents($output, $csv);
$outputInterface->writeln("<info>Results saved to: $output</info>");
}
return 0;
});
// Utility command
$app->command('utils:hash file [--algorithm=]', function ($file, $algorithm, $output) {
$algorithm = $algorithm ?: 'md5';
if (!file_exists($file)) {
$output->writeln("<error>File not found: $file</error>");
return 1;
}
$supportedAlgorithms = ['md5', 'sha1', 'sha256'];
if (!in_array($algorithm, $supportedAlgorithms)) {
$output->writeln("<error>Unsupported algorithm. Use: " . implode(', ', $supportedAlgorithms) . "</error>");
return 1;
}
$hash = hash_file($algorithm, $file);
$output->writeln("<info>File:</info> $file");
$output->writeln("<info>Algorithm:</info> $algorithm");
$output->writeln("<info>Hash:</info> $hash");
return 0;
});
$app->run();
DI Container Integration
#!/usr/bin/env php
<?php
// di-example.php
require_once 'vendor/autoload.php';
use Silly\Application;
use Psr\Container\ContainerInterface;
// Simple DI container implementation
class SimpleContainer implements ContainerInterface
{
private array $services = [];
public function set(string $id, $service): void
{
$this->services[$id] = $service;
}
public function get(string $id)
{
if (!$this->has($id)) {
throw new \Exception("Service not found: $id");
}
$service = $this->services[$id];
if (is_callable($service)) {
return $service($this);
}
return $service;
}
public function has(string $id): bool
{
return isset($this->services[$id]);
}
}
// Service definitions
class DatabaseService
{
public function connect(): string
{
return "Connected to database";
}
public function query(string $sql): array
{
return ["Result for: $sql"];
}
}
class LoggerService
{
public function log(string $message): void
{
echo "[" . date('Y-m-d H:i:s') . "] $message\n";
}
}
// Container setup
$container = new SimpleContainer();
$container->set('database', function () {
return new DatabaseService();
});
$container->set('logger', function () {
return new LoggerService();
});
// Create application
$app = new Application('DI Example', '1.0.0');
$app->useContainer($container);
// Commands using services
$app->command('db:status', function (DatabaseService $database, LoggerService $logger, $output) {
$logger->log('Checking database status...');
$status = $database->connect();
$output->writeln("<info>$status</info>");
$logger->log('Database status check completed');
});
$app->run();
Testing Example
<?php
// tests/CliTest.php
use PHPUnit\Framework\TestCase;
use Silly\Application;
use Symfony\Component\Console\Tester\ApplicationTester;
class CliTest extends TestCase
{
private Application $app;
private ApplicationTester $tester;
protected function setUp(): void
{
$this->app = new Application();
// Add test command
$this->app->command('test:hello [name]', function ($name, $output) {
$name = $name ?: 'World';
$output->writeln("Hello $name!");
});
$this->tester = new ApplicationTester($this->app);
}
public function testHelloCommand(): void
{
$this->tester->run(['command' => 'test:hello']);
$this->assertEquals(0, $this->tester->getStatusCode());
$this->assertStringContains('Hello World!', $this->tester->getDisplay());
}
public function testHelloCommandWithName(): void
{
$this->tester->run([
'command' => 'test:hello',
'name' => 'PHP'
]);
$this->assertEquals(0, $this->tester->getStatusCode());
$this->assertStringContains('Hello PHP!', $this->tester->getDisplay());
}
}
composer.json Configuration
{
"name": "my/cli-tool",
"description": "My CLI tool built with Silly",
"type": "project",
"require": {
"php": "^8.0",
"silly/silly": "^1.8"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"bin": ["cli-app.php"],
"scripts": {
"test": "phpunit tests/"
}
}