Skip to main content

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​

CommandWhat it does
migrate:updateMigrate up or down to a version, or to the latest
migrate:up / migrate:downMigrate in one direction
migrate:resetRecreate the database from db/base.sql, then migrate
migrate:versionShow the current database version (alias: migrate:status)
migrate:createCreate the migration version table (alias: migrate:install)
codegen:allGenerate every component for a table
codegen:model, codegen:repository, codegen:service, codegen:controller, codegen:testGenerate one component (codegen:repo is an alias)
openapiRegenerate public/docs/openapi.json from the controller attributes, and compile it into generated/ (routes.php, openapi.php)
openapi:checkFail when the spec, or a file compiled from it, is not what its sources generate
terminalOpen the interactive PHP terminal
install / uninstallInstall 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 scriptRuns
composer migrate -- --env=dev updatephp gluo migrate --env=dev update
composer codegen -- --env=dev --table=product all --savephp gluo codegen --env=dev --table=product all --save
composer openapiphp gluo openapi
composer openapi:checkphp gluo openapi:check
composer terminalphp 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" becomes int $days).
  • --help prints the method's PHPDoc and a usage line built from its signature.
  • Names are lowercase, group:action, using a-z, 0-9, : and -. The exact name of a built-in command is reserved, but the groups are open: migrate:seed is 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 with new, so its constructor dependencies are injected as everywhere else in the application.
  • The method must be public.
  • php gluo list describes 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 helpers protected or private.
  • Method names become kebab-case: cleanOldRecords and clean_old_records both give clean-old-records.
  • A name produced twice is an error, for every php gluo command 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, --help prints the method's PHPDoc, and php gluo list --check checks 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
  • --env is required: it is the environment the service runs in.
  • --template is systemd (default), crond, upstart or initd. Every template but crond adds --daemon, which calls the method again every second.
  • The service is named after the project directory and the command (shop-payment-sweep); --name chooses another, and uninstall takes 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 statusMeaningApplies to
NO_ENVNeither --env nor APP_ENV is setmigrate:*, codegen:*
CONFIG_ERRORThe configuration fails to load for the environmentmigrate:*, codegen:*
NO_DB_CONFIGDBDRIVER_CONNECTION is not definedmigrate:*, codegen:*
DB_UNREACHABLEThe 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_EXTENSIONA required PHP extension is not loaded (readline)terminal
Project statusMeaning
INVALID_NAMEThe name has characters outside a-z, 0-9, : and -
RESERVEDThe name is the exact name of a built-in command, or list
INVALID_TARGETThe target is neither Class::method nor an existing class
MISSING_CLASSThe class does not exist
MISSING_METHODThe method does not exist
NOT_PUBLICThe method is not public
NOT_IN_CONTAINERThe class is not registered in the container
ERRORThe 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.