BEAR.Sunday CLI Tutorial
Prerequisites
- PHP 8.2 or higher
- Composer
- Git
Step 1: Project Creation
1.1 Create a New Project
composer create-project -n bear/skeleton MyVendor.Greet
cd MyVendor.Greet
1.2 Verify Development Server
php -S 127.0.0.1:8080 -t public
Access http://127.0.0.1:8080 in your browser and confirm that “Hello BEAR.Sunday” is displayed.
{
"greeting": "Hello BEAR.Sunday",
"_links": {
"self": {
"href": "/index"
}
}
}
Step 2: Install BEAR.Cli
composer require bear/cli
Step 3: Create Greeting Resource
Create src/Resource/Page/Greeting.php
:
<?php
namespace MyVendor\Greet\Resource\Page;
use BEAR\Cli\Attribute\Cli;
use BEAR\Cli\Attribute\Option;
use BEAR\Resource\ResourceObject;
class Greeting extends ResourceObject
{
#[Cli(
name: 'greet',
description: 'Generate a greeting message',
output: 'message'
)]
public function onGet(
#[Option(shortName: 'n', description: 'Name to greet')]
string $name = 'World',
#[Option(shortName: 'l', description: 'Language (en, ja, fr, es)')]
string $lang = 'en'
): static {
$greeting = match ($lang) {
'ja' => 'こんにちは',
'fr' => 'Bonjour',
'es' => '¡Hola',
default => 'Hello',
};
$this->body = [
'message' => "{$greeting}, {$name}!",
'language' => $lang
];
return $this;
}
}
Step 4: Generate CLI Command
Generate the CLI command using your application namespace:
$ vendor/bin/bear-cli-gen 'MyVendor\Greet'
# Generated files:
# bin/cli/greet # CLI command
# var/homebrew/greet.rb # Homebrew formula (if Git repository is configured)
Step 5: Test the CLI Command
5.1 Basic Usage
$ bin/cli/greet --help
Generate a greeting message
Usage: greet [options]
Options:
--name, -n Name to greet (default: World)
--lang, -l Language (en, ja, fr, es) (default: en)
--help, -h Show this help message
$ bin/cli/greet
Hello, World!
$ bin/cli/greet -n "Alice" -l ja
こんにちは, Alice!
5.2 Advanced Examples
# French greeting
$ bin/cli/greet --name "Pierre" --lang fr
Bonjour, Pierre!
# Spanish greeting
$ bin/cli/greet -n "Carlos" -l es
¡Hola, Carlos!
Step 6: Add More Complex Features
6.1 Add Time-Based Greetings
Update the Greeting
resource to include time-based greetings:
<?php
namespace MyVendor\Greet\Resource\Page;
use BEAR\Cli\Attribute\Cli;
use BEAR\Cli\Attribute\Option;
use BEAR\Resource\ResourceObject;
use DateTimeImmutable;
class Greeting extends ResourceObject
{
#[Cli(
name: 'greet',
description: 'Generate a time-aware greeting message',
output: 'message'
)]
public function onGet(
#[Option(shortName: 'n', description: 'Name to greet')]
string $name = 'World',
#[Option(shortName: 'l', description: 'Language (en, ja, fr, es)')]
string $lang = 'en',
#[Option(shortName: 't', description: 'Include time-based greeting')]
bool $timeGreeting = false
): static {
$greeting = $this->getGreeting($lang, $timeGreeting);
$this->body = [
'message' => "{$greeting}, {$name}!",
'language' => $lang,
'time' => (new DateTimeImmutable())->format('Y-m-d H:i:s')
];
return $this;
}
private function getGreeting(string $lang, bool $timeGreeting): string
{
$baseGreeting = match ($lang) {
'ja' => 'こんにちは',
'fr' => 'Bonjour',
'es' => '¡Hola',
default => 'Hello',
};
if (!$timeGreeting) {
return $baseGreeting;
}
$hour = (int) (new DateTimeImmutable())->format('H');
return match ($lang) {
'ja' => match (true) {
$hour < 12 => 'おはようございます',
$hour < 18 => 'こんにちは',
default => 'こんばんは'
},
'fr' => match (true) {
$hour < 12 => 'Bonjour',
$hour < 18 => 'Bon après-midi',
default => 'Bonsoir'
},
'es' => match (true) {
$hour < 12 => 'Buenos días',
$hour < 18 => 'Buenas tardes',
default => 'Buenas noches'
},
default => match (true) {
$hour < 12 => 'Good morning',
$hour < 18 => 'Good afternoon',
default => 'Good evening'
}
};
}
}
6.2 Test Enhanced Features
# Regenerate CLI command after changes
$ vendor/bin/bear-cli-gen 'MyVendor\Greet'
# Test time-based greetings
$ bin/cli/greet -n "Alice" -l en -t
Good morning, Alice! # (if run in the morning)
$ bin/cli/greet -n "田中" -l ja -t
おはようございます, 田中! # (if run in the morning)
Step 7: Testing
7.1 Create Unit Tests
Create tests/Resource/Page/GreetingTest.php
:
<?php
namespace MyVendor\Greet\Resource\Page;
use BEAR\Resource\ResourceInterface;
use MyVendor\Greet\Injector;
use PHPUnit\Framework\TestCase;
class GreetingTest extends TestCase
{
private ResourceInterface $resource;
protected function setUp(): void
{
$this->resource = Injector::getInstance('test-cli-app')
->getInstance(ResourceInterface::class);
}
public function testDefaultGreeting(): void
{
$response = $this->resource->get('page://self/greeting');
$this->assertSame(200, $response->code);
$this->assertSame('Hello, World!', $response->body['message']);
$this->assertSame('en', $response->body['language']);
}
public function testJapaneseGreeting(): void
{
$response = $this->resource->get('page://self/greeting', [
'name' => '太郎',
'lang' => 'ja'
]);
$this->assertSame('こんにちは, 太郎!', $response->body['message']);
$this->assertSame('ja', $response->body['language']);
}
public function testTimeBasedGreeting(): void
{
$response = $this->resource->get('page://self/greeting', [
'name' => 'Alice',
'lang' => 'en',
'timeGreeting' => true
]);
$this->assertStringContains('Alice!', $response->body['message']);
$this->assertArrayHasKey('time', $response->body);
}
}
7.2 Run Tests
$ composer test
Step 8: Deployment and Distribution
8.1 GitHub Repository Setup
If you have a GitHub repository configured, a Homebrew formula will be generated automatically. You can distribute your CLI tool via Homebrew:
# Create a tap repository
$ git clone https://github.com/yourusername/homebrew-tap.git
$ cp var/homebrew/greet.rb homebrew-tap/greet.rb
$ cd homebrew-tap
$ git add greet.rb
$ git commit -m "Add greet formula"
$ git push
8.2 Install via Homebrew
Users can then install your CLI tool:
$ brew tap yourusername/tap
$ brew install greet
$ greet -n "User" -l en
Hello, User!
Conclusion
This tutorial has demonstrated more than just CLI tool creation—it has revealed the essential value of BEAR.Sunday:
The True Value of Resource-Oriented Architecture
One Resource, Multiple Boundaries
- The
Greeting
resource functions as Web API, CLI, and Homebrew package with a single implementation - No duplication of business logic, maintenance in one place
Boundary-Crossing Framework
BEAR.Sunday functions as a boundary framework, transparently handling:
- Protocol boundaries: HTTP ↔ Command line
- Interface boundaries: Web ↔ CLI ↔ Package distribution
- Environment boundaries: Development ↔ Production ↔ User environments
Design Philosophy in Action
// One resource
class Greeting extends ResourceObject {
public function onGet(string $name, string $lang = 'en'): static
{
// Business logic in one place
}
}
↓
# As Web API
curl "http://localhost/greeting?name=World&lang=ja"
# As CLI
./bin/cli/greet -n "World" -l ja
# As Homebrew package
brew install your-vendor/greet && greet -n "World" -l ja
Long-term Maintainability and Productivity
- DRY Principle: Domain logic is not coupled with interfaces
- Unified Testing: Testing one resource covers all boundaries
- Consistent API Design: Same parameter structure for Web API and CLI
- Future Extensibility: New boundaries (gRPC, GraphQL, etc.) can use the same resource
- PHP Version Independence: Freedom to continue using what works
Integration with Modern Distribution Systems
BEAR.Sunday resources integrate naturally with modern package systems. By leveraging package managers like Homebrew and the Composer ecosystem, users can utilize tools through unified interfaces without being aware of the execution environment.
BEAR.Sunday’s “Because Everything is a Resource” is not just a slogan, but a design philosophy that realizes consistency and maintainability across boundaries. As experienced in this tutorial, resource-oriented architecture creates boundary-free software and brings new horizons to both development and user experiences.
Next Steps
- Explore more complex CLI patterns
- Add configuration file support
- Implement subcommands
- Add logging and error handling
- Create interactive CLI interfaces
For more information, see the CLI documentation and BEAR.Cli repository.