The gluo Command
php gluo is the command-line entry point of a Gluo project. It runs two kinds of commands:
- Built-in commands, provided by
byjg/gluo-core: migrations, code generation, the OpenAPI spec and the interactive terminal. - Project commands, defined by your application: any public method, by a short name.
php gluo # same as php gluo list
php gluo list [--check] # list built-in and project commands
php gluo [--env=<env>] <command> [args...]
php gluo <command> --help # help for the command
Run it from the project root. It exits with 0 on success and 1 on any failure (unknown
command, exception from the command, list --check with a status other than OK).
Add -v to print the stack trace of a failure.
Selecting the environment
--env=<env>, --env <env> or -e <env>, before or after the command name:
php gluo --env=dev migrate:update
php gluo migrate:update --env dev
APP_ENV=dev php gluo migrate:update # the existing APP_ENV applies when --env is absent
The launcher consumes the option and exports it as APP_ENV before the configuration is
loaded. It is not passed to the command.
Built-in commands
| Command | What it does |
|---|---|
migrate:update | Migrate up or down to a version, or to the latest |
migrate:up / migrate:down | Migrate in one direction |
migrate:reset | Recreate the database from db/base.sql, then migrate |
migrate:version | Show the current database version (alias: migrate:status) |
migrate:create | Create the migration version table (alias: migrate:install) |
codegen:all | Generate every component for a table |
codegen:model, codegen:repository, codegen:service, codegen:controller, codegen:test | Generate one component (codegen:repo is an alias) |
openapi | Regenerate public/docs/openapi.json from the controller attributes, and compile it into generated/ (routes.php, openapi.php) |
openapi:check | Fail when the spec, or a file compiled from it, is not what its sources generate |
terminal | Open the interactive PHP terminal |
install / uninstall | Install a project command as a system service, or remove it (below) |
Everything after the command name is passed to the command unchanged:
php gluo --env=dev migrate:reset --version=5
php gluo --env=dev migrate:up -vv
php gluo --env=dev codegen:all --table=product --save
php gluo --env=dev codegen:model --table=note --activerecord
See Database Migrations and the Code Generator for the options of each.
The built-ins run through the project's builder/Scripts.php, so anything you override
there (getAppNamespace(), the OpenAPI scan paths, extra migration drivers) applies.
Composer scripts
The Composer scripts are aliases, so existing pipelines keep working:
| Composer script | Runs |
|---|---|
composer migrate -- --env=dev update | php gluo migrate --env=dev update |
composer codegen -- --env=dev --table=product all --save | php gluo codegen --env=dev --table=product all --save |
composer openapi | php gluo openapi |
composer openapi:check | php gluo openapi:check |
composer terminal | php gluo terminal |
php gluo migrate <command> and php gluo codegen <components>, without the colon, exist for
these aliases. The codegen form is also the way to generate several components in one
run: php gluo --env=dev codegen --table=product model controller --save.
composer test, composer psalm and composer compile are development tooling and stay
as Composer scripts.
Project commands
Declare them in src/Command/Commands.php, the project's CommandProvider, as a command
name mapped to a method. The gluo script passes this class to the launcher.
<?php
namespace MyApp\Command;
use ByJG\Scriptify\CommandProvider;
use MyApp\Service\MailService;
use MyApp\Service\MaintenanceService;
use MyApp\Service\SeedService;
class Commands implements CommandProvider
{
#[\Override]
public function commands(): array
{
return [
'user:cleanup' => MaintenanceService::class . '::cleanOldRecords',
'mail:welcome' => MailService::class . '::sendWelcomeEmail',
'migrate:seed' => SeedService::class . '::run',
];
}
}
The targets are class references (::class), so a typo fails in the IDE and in Psalm,
and a rename is refactor-safe.
/**
* Delete records older than the given number of days.
* @param int $days
* @param string|null $table
*/
public function cleanOldRecords(int $days, ?string $table = null): void
php gluo user:cleanup 30
php gluo user:cleanup 30 audit_log
php gluo --env=prod user:cleanup 30
php gluo user:cleanup --help
- Arguments are positional. Each value after the command name maps, in order, to the
method parameters. They arrive as strings, and PHP converts them to the scalar parameter
types (
"30"becomesint $days). --helpprints the method's PHPDoc and a usage line built from its signature.- Names are lowercase,
group:action, usinga-z,0-9,:and-. The exact name of a built-in command is reserved, but the groups are open:migrate:seedis a valid project command. - The class must be registered in the container (
config/dev/05-services.php, for example). It is built by the container, never withnew, so its constructor dependencies are injected as everywhere else in the application. - The method must be public.
php gluo listdescribes the command with the first line of the method's PHPDoc (Delete records older than the given number of days.), or shows the target when the method has no PHPDoc.
A class as a group of commands
Map a name to a class, without ::, and every public method the class declares becomes a
command under that name, in kebab-case:
return [
'payment' => PaymentCommands::class, // payment:create, payment:refund, ...
'user:cleanup' => MaintenanceService::class . '::cleanOldRecords',
];
class PaymentCommands
{
public function __construct(private PaymentWorkflow $workflow) {}
/** Create a payment and send it to the gateway. */
public function create(float $amount): void { /* ... */ } // php gluo payment:create 10.50
/** Refund a paid payment, inside the refund window. */
public function refund(int $paymentId): void { /* ... */ } // php gluo payment:refund 42
public function cleanOldRecords(int $days): void { /* ... */ } // php gluo payment:clean-old-records 30
protected function say(string $line): void { /* ... */ } // not a command
}
- Only the public, non-static methods declared by the class become commands. The
constructor, magic methods (
__*), static methods and inherited methods do not; keep helpersprotectedorprivate. - Method names become kebab-case:
cleanOldRecordsandclean_old_recordsboth giveclean-old-records. - A name produced twice is an error, for every
php gluocommand until it is fixed: an explicit'payment:create'entry next to'payment' => ...PaymentCommands, or two methods with the same kebab-case name. - A method added to the class is a command at once, with no change to
Commands. - The rest is the same as for a single method: the class must be in the container,
--helpprints the method's PHPDoc, andphp gluo list --checkchecks each command.
Commands are run by Scriptify's launcher, with the Gluo container
(Config::getContainer()). php gluo list and the built-ins never build it.
Running a command as a service
php gluo install installs a project command as a system service; it needs root, like any
service file:
sudo php gluo --env=prod install payment:sweep # systemd, runs it again every second
sudo php gluo --env=prod install --template=crond payment:sweep # once a minute
sudo php gluo --env=prod install --name=sweeper --description="Payment sweep" payment:sweep 24
sudo php gluo uninstall payment:sweep
The service runs php gluo itself, so the Gluo container is built when it starts, exactly
as on the command line. Its command line is written in full, nothing left to a default:
ExecStart=/usr/bin/php '/var/www/shop/gluo' --env='prod' payment:sweep '24' --daemon
WorkingDirectory=/var/www/shop
--envis required: it is the environment the service runs in.--templateissystemd(default),crond,upstartorinitd. Every template butcrondadds--daemon, which calls the method again every second.- The service is named after the project directory and the command (
shop-payment-sweep);--namechooses another, anduninstalltakes the same--name. - The command must pass
php gluo list --check, and installing runs nothing.
Checking the commands
php gluo list only lists. php gluo list --check adds a status to each command, without
running any of them — useful during development and in CI:
Environment: dev
Built-in
codegen:all OK
migrate:update DB_UNREACHABLE SQLSTATE[HY000] [2002] Connection refused
openapi OK
terminal OK
Project
mail:welcome OK MyApp/Service/MailService::sendWelcomeEmail
queue:work NOT_IN_CONTAINER MyApp/Service/QueueWorker::processLoop
user:cleanup MISSING_METHOD MyApp/Service/MaintenanceService::cleanOldRecord
It honours --env, so php gluo --env=prod list --check validates the production
configuration, and it exits with 1 unless every command is OK.
| Built-in status | Meaning | Applies to |
|---|---|---|
NO_ENV | Neither --env nor APP_ENV is set | migrate:*, codegen:* |
CONFIG_ERROR | The configuration fails to load for the environment | migrate:*, codegen:* |
NO_DB_CONFIG | DBDRIVER_CONNECTION is not defined | migrate:*, codegen:* |
DB_UNREACHABLE | The database connection fails (the password is masked in the message: always inside a connection URL, elsewhere when it has 4 or more characters) | migrate:*, codegen:* |
MISSING_EXTENSION | A required PHP extension is not loaded (readline) | terminal |
| Project status | Meaning |
|---|---|
INVALID_NAME | The name has characters outside a-z, 0-9, : and - |
RESERVED | The name is the exact name of a built-in command, or list |
INVALID_TARGET | The target is neither Class::method nor an existing class |
MISSING_CLASS | The class does not exist |
MISSING_METHOD | The method does not exist |
NOT_PUBLIC | The method is not public |
NOT_IN_CONTAINER | The class is not registered in the container |
ERROR | The check itself failed, for example because the container could not be loaded |
The check never builds your classes: it uses reflection and asks the container whether it has the class, so it does not open the connections or clients a constructor would. The one exception is the database, for the built-ins that need it: one connection is opened and closed, with no query and without touching the migration version table.