Console

Melodic provides a lightweight console runner for building CLI commands, plus a scaffolding CLI for generating projects and CQRS entity stacks. The system includes the melodic binary with scaffolding commands, a Console runner, a Command base class with output helpers, and built-in commands for listing routes and clearing the cache.

The melodic CLI

The framework ships with a bin/melodic binary that provides project and entity scaffolding. After installing the framework via Composer, run it with vendor/bin/melodic:

$ vendor/bin/melodic

Melodic 1.7.2
Available commands:
  make:project             Create a new Melodic project (full, api, or mvc)
  make:entity              Generate CQRS entity files (DTO, queries, commands, service, controller)
  make:config              Create an environment configuration file
  claude:install           Install Claude Code agents and skills into your project

make:project

Creates a new Melodic project with the canonical directory structure, configuration, entry points, a service provider, and a console script.

# Create a full project — MVC + API (default)
vendor/bin/melodic make:project my-app

# API-only project
vendor/bin/melodic make:project my-api --type=api

# MVC-only project
vendor/bin/melodic make:project my-site --type=mvc

Project Types

TypeFlagDescription
Full (default)--type=full or omitMVC views + API routing — recommended for most apps
API only--type=apiNo views directory, API-only index.php
MVC only--type=mvcViews + HomeController, MVC-only index.php

The default full type generates the following structure:

my-app/
  composer.json               PSR-4 autoloading configured
  config/
    config.json               Default configuration (database, JWT, CORS)
  public/
    index.php                 HTTP entry point (MVC + API routes)
    .htaccess                 Apache rewrite rules
  bin/
    console                   CLI entry point
  src/
    Controllers/
    Services/
    DTO/
    Data/
    Middleware/
    Providers/
      AppServiceProvider.php  Ready-to-use service provider
  views/
    layouts/
      main.phtml              Base HTML layout
    home/
      index.phtml             Home page template
  storage/
    cache/
    logs/
  tests/
  .gitignore

API-only projects omit the views/ directory and HomeController. MVC-only projects include views but use an MVC-only index.php without API route stubs.

After generating, follow the printed instructions:

$ vendor/bin/melodic make:project my-app

Creating full project 'my-app'...

Project 'my-app' created successfully!

Next steps:
  cd my-app
  composer install
  php -S localhost:8080 -t public

make:entity

Generates a full CQRS entity stack — 8 files per entity — following the framework's naming conventions. Run this from inside your project directory:

cd my-api
vendor/bin/melodic make:entity Church

This generates:

File Purpose
src/DTO/ChurchModel.php Data transfer object extending Model
src/Data/Church/Queries/GetAllChurchesQuery.php Query to fetch all records
src/Data/Church/Queries/GetChurchByIdQuery.php Query to fetch a single record by ID
src/Data/Church/Commands/CreateChurchCommand.php Command to insert a record
src/Data/Church/Commands/UpdateChurchCommand.php Command to update a record
src/Data/Church/Commands/DeleteChurchCommand.php Command to delete a record
src/Services/ChurchService.php Service orchestrating queries and commands
src/Controllers/ChurchController.php API controller with full CRUD actions

Safe to re-run. Existing files are never overwritten. The command skips any file that already exists and reports which files were created or skipped.

The entity name is automatically converted to the appropriate casing:

InputEntityPluralTable Name
ChurchChurchChurcheschurches
blog-postBlogPostBlogPostsblog_posts
user_roleUserRoleUserRolesuser_roles

The namespace is read from your project's composer.json PSR-4 autoload configuration.

Tip: After generating, fill in the SQL statements and model properties in the generated files, then register the entity's routes in your public/index.php:

$router->apiResource('/api/churches', ChurchController::class);

make:config

Creates an environment configuration file with a minimal template. Use this to scaffold config overrides for a new environment:

vendor/bin/melodic make:config staging

This creates config/config.staging.json with sensible defaults. The command refuses to overwrite an existing file, so it is safe to run even if you are unsure whether the file already exists.

The generated file contains a minimal template that you can customize for your environment:

{
    "app": {
        "debug": false
    },
    "database": {
        "dsn": ""
    },
    "jwt": {
        "secret": "",
        "algorithm": "HS256"
    }
}

Once created, the file is automatically loaded by loadEnvironmentConfig() when the APP_ENV environment variable matches the environment name. See the Configuration docs for details on the loading order.

claude:install

Installs Melodic-specific Claude Code agents and skills into your project. This sets up AI-assisted development tooling that understands the framework's architecture, conventions, and patterns.

vendor/bin/melodic claude:install

This installs:

TypeNamePurpose
Agentmelodic-expertFramework expert for architecture, patterns, and debugging
Skill/melodic:scaffold-appScaffold a new Melodic application
Skill/melodic:scaffold-resourceScaffold a CQRS resource (Model, Queries, Commands, Service, Controller)
Skill/melodic:add-middlewareScaffold a middleware class

The command also generates a CLAUDE.md file at your project root with framework conventions, naming patterns, and architecture documentation for Claude Code to reference.

Safe to re-run. Existing files are never overwritten. Use --force to replace previously installed files: vendor/bin/melodic claude:install --force

Console Runner

The Melodic\Console\Console class is the entry point for CLI applications. It manages command registration, dispatches to the correct command based on $argv, and provides built-in help output.

<?php
use Melodic\Console\Console;

$console = new Console();
$console->setName('My App');

$console->register($routeListCommand);
$console->register($cacheClearCommand);

exit($console->run($argv));

Version tracking. The Console class automatically reads the version from Melodic\Framework::VERSION. You can override it with setVersion() if your application tracks its own version separately.

How Dispatching Works

The run() method reads the command name from $argv[1]. Any arguments after the command name are passed to the command's execute() method as a string array. The method returns an integer exit code.

Input Behavior Exit Code
No arguments Shows help listing 0
help Shows help listing 0
Valid command name Executes the command Returned by the command
Unknown command name Prints error, shows help listing 1

Help Output

When run with no arguments or the help command, the console prints the application name, version, and a list of all registered commands with their descriptions:

$ php bin/console

My App 1.7.2
Available commands:
  route:list              List all registered routes
  cache:clear             Clear the application cache

CommandInterface

The Melodic\Console\CommandInterface defines the contract that all commands must implement. You can implement this interface directly for full control, or extend the Command base class for convenience.

<?php
use Melodic\Console\CommandInterface;

interface CommandInterface
{
    public function getName(): string;
    public function getDescription(): string;
    public function execute(array $args): int;
}
Method Return Type Description
getName() string The command name used on the CLI (e.g., 'route:list')
getDescription() string A short description shown in the help listing
execute() int Run the command logic; return 0 for success, non-zero for failure

Command Base Class

The Melodic\Console\Command abstract class implements CommandInterface and provides the constructor and output helpers. Extend this class for most commands.

<?php
use Melodic\Console\Command;

abstract class Command implements CommandInterface
{
    public function __construct(
        private readonly string $name,
        private readonly string $description,
    ) {}

    // Output helpers below...
}

Output Helpers

The base class provides four output methods for writing to the terminal.

Method Signature Description
writeln() writeln(string $text): void Write text followed by a newline to STDOUT
write() write(string $text): void Write text to STDOUT without a trailing newline
error() error(string $text): void Write text followed by a newline to STDERR
table() table(array $headers, array $rows): void Render an ASCII table with borders to STDOUT

Table Output

The table() method renders a formatted ASCII table with automatic column width calculation. Column widths are determined by the longest value in each column (including headers).

<?php
$this->table(
    ['Name', 'Email', 'Role'],
    [
        ['Alice', 'alice@example.com', 'Admin'],
        ['Bob', 'bob@example.com', 'Editor'],
    ],
);

Produces:

+-------+-------------------+--------+
| Name  | Email             | Role   |
+-------+-------------------+--------+
| Alice | alice@example.com | Admin  |
| Bob   | bob@example.com   | Editor |
+-------+-------------------+--------+

Built-in Commands

Melodic ships with two built-in commands that you can register immediately.

route:list

The Melodic\Console\RouteListCommand displays all registered routes in a table with the HTTP method, path, controller class, and action method.

<?php
use Melodic\Console\RouteListCommand;
use Melodic\Routing\Router;

$router = $container->get(Router::class);
$console->register(new RouteListCommand($router));

Running php bin/console route:list produces:

+--------+------------------+--------------------+--------+
| Method | Path             | Controller         | Action |
+--------+------------------+--------------------+--------+
| GET    | /                | HomeController     | index  |
| GET    | /api/users       | UserController     | index  |
| POST   | /api/users       | UserController     | store  |
| GET    | /api/users/{id}  | UserController     | show   |
| PUT    | /api/users/{id}  | UserController     | update |
| DELETE | /api/users/{id}  | UserController     | delete |
+--------+------------------+--------------------+--------+

Debugging routes. Use route:list to verify that your route registrations, groups, and API resources are resolving correctly. It reads directly from the Router instance.

cache:clear

The Melodic\Console\CacheClearCommand calls clear() on the injected CacheInterface to remove all cached entries.

<?php
use Melodic\Console\CacheClearCommand;
use Melodic\Cache\CacheInterface;

$cache = $container->get(CacheInterface::class);
$console->register(new CacheClearCommand($cache));
$ php bin/console cache:clear
Cache cleared successfully.

The command returns exit code 0 on success. If clear() returns false, it writes an error to STDERR and returns exit code 1.

Creating Custom Commands

To create a custom command, extend Melodic\Console\Command, call the parent constructor with a name and description, and implement the execute() method.

Example: GreetCommand

<?php
declare(strict_types=1);

namespace App\Commands;

use Melodic\Console\Command;

class GreetCommand extends Command
{
    public function __construct()
    {
        parent::__construct('greet', 'Greet a user by name');
    }

    public function execute(array $args): int
    {
        $name = $args[0] ?? 'World';

        $this->writeln("Hello, {$name}!");

        return 0;
    }
}
$ php bin/console greet Alice
Hello, Alice!

$ php bin/console greet
Hello, World!

Arguments. The $args array contains everything after the command name. For php bin/console greet Alice --loud, the array would be ['Alice', '--loud']. Argument parsing is left to your command — there is no built-in flag parser.

Example: Command with Table Output

<?php
declare(strict_types=1);

namespace App\Commands;

use Melodic\Console\Command;

class UserListCommand extends Command
{
    public function __construct(
        private readonly UserService $userService,
    ) {
        parent::__construct('user:list', 'List all users');
    }

    public function execute(array $args): int
    {
        $users = $this->userService->getAll();

        if (count($users) === 0) {
            $this->writeln('No users found.');
            return 0;
        }

        $this->table(
            ['ID', 'Name', 'Email'],
            array_map(fn($u) => [(string) $u->id, $u->name, $u->email], $users),
        );

        return 0;
    }
}

Example: Command with Error Handling

<?php
declare(strict_types=1);

namespace App\Commands;

use Melodic\Console\Command;

class ImportCommand extends Command
{
    public function __construct()
    {
        parent::__construct('data:import', 'Import data from a CSV file');
    }

    public function execute(array $args): int
    {
        if (empty($args[0])) {
            $this->error('Usage: data:import <filename>');
            return 1;
        }

        $filename = $args[0];

        if (!file_exists($filename)) {
            $this->error("File not found: {$filename}");
            return 1;
        }

        $this->writeln("Importing from {$filename}...");

        // ... import logic ...

        $this->writeln('Import complete.');
        return 0;
    }
}

Setting Up a Console Entry Point

Create a bin/console script at your project root to serve as the CLI entry point. This script bootstraps the application, registers commands, and runs the console.

#!/usr/bin/env php
<?php
declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use Melodic\Core\Application;
use Melodic\Console\Console;
use Melodic\Console\RouteListCommand;
use Melodic\Console\CacheClearCommand;
use Melodic\Routing\Router;
use Melodic\Cache\CacheInterface;

// Bootstrap the application
$app = new Application(dirname(__DIR__));
$app->loadEnvironmentConfig();

$app->services(function ($container) {
    // Register your service providers...
});

$app->routes(function ($router) {
    // Register your routes (needed for route:list)...
});

// Build the console
$console = new Console();
$console->setName('My App');
$console->setVersion('1.0.0');

// Register built-in commands
$container = $app->getContainer();
$console->register(new RouteListCommand($container->get(Router::class)));
$console->register(new CacheClearCommand($container->get(CacheInterface::class)));

// Register custom commands
$console->register(new App\Commands\GreetCommand());

exit($console->run($argv));

Make it executable. After creating bin/console, run chmod +x bin/console so you can invoke it directly as ./bin/console instead of php bin/console.

Project Structure

my-app/
  bin/
    console              ← CLI entry point
  config/
    config.json
  public/
    index.php            ← HTTP entry point
  src/
    Commands/
      GreetCommand.php
      UserListCommand.php
  vendor/
  composer.json

Console API Reference

Console Class

Method Signature Description
register() register(CommandInterface $command): void Register a command with the console
setName() setName(string $name): void Set the application name shown in help output
setVersion() setVersion(string $version): void Set the version string shown in help output
run() run(array $argv): int Parse argv, dispatch to a command, and return its exit code

Command Base Class

Method Visibility Description
__construct(string $name, string $description) public Set the command name and description
getName(): string public Return the command name
getDescription(): string public Return the command description
execute(array $args): int public (abstract) Implement command logic; return exit code
writeln(string $text): void protected Write to STDOUT with newline
write(string $text): void protected Write to STDOUT without newline
error(string $text): void protected Write to STDERR with newline
table(array $headers, array $rows): void protected Render an ASCII table with auto-sized columns

Class Reference

Class Namespace Purpose
Console Melodic\Console CLI runner: registers commands, parses argv, dispatches execution
CommandInterface Melodic\Console Contract for all console commands
Command Melodic\Console Abstract base class with name, description, and output helpers
RouteListCommand Melodic\Console Built-in command that lists all registered routes in a table
CacheClearCommand Melodic\Console Built-in command that clears the application cache
MakeProjectCommand Melodic\Console\Make Scaffolds a new API or MVC project with canonical directory structure
MakeEntityCommand Melodic\Console\Make Generates 8 CQRS files for an entity (DTO, queries, commands, service, controller)
MakeConfigCommand Melodic\Console\Make Creates an environment configuration file with a minimal template
Stub Melodic\Console\Make Template rendering and string utilities (pascalCase, camelCase, snakeCase, pluralize)
ClaudeInstallCommand Melodic\Console Installs Claude Code agents and skills into a Melodic project