Pular para o conteúdo principal

The gluo Command

./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.
./gluo # same as ./gluo list
./gluo list [--check] # list built-in and project commands
./gluo [--env=<env>] <command> [args...]
./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:

./gluo --env=dev migrate:update
./gluo migrate:update --env dev
APP_ENV=dev ./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
openapi:checkWarn when the spec is older than its sources
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:

./gluo --env=dev migrate:reset --version=5
./gluo --env=dev migrate:up -vv
./gluo --env=dev codegen:all --table=product --save
./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 update./gluo migrate --env=dev update
composer codegen -- --env=dev --table=product all --save./gluo codegen --env=dev --table=product all --save
composer openapi./gluo openapi
composer openapi:check./gluo openapi:check
composer terminal./gluo terminal

./gluo migrate <command> and ./gluo codegen <components>, without the colon, exist for these aliases. The codegen form is also the way to generate several components in one run: ./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
./gluo user:cleanup 30
./gluo user:cleanup 30 audit_log
./gluo --env=prod user:cleanup 30
./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.
  • ./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 { /* ... */ } // ./gluo payment:create 10.50

/** Refund a paid payment, inside the refund window. */
public function refund(int $paymentId): void { /* ... */ } // ./gluo payment:refund 42

public function cleanOldRecords(int $days): void { /* ... */ } // ./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 ./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 ./gluo list --check checks each command.

Commands are run by Scriptify's launcher, with the Gluo container (Config::getContainer()). ./gluo list and the built-ins never build it.

Running a command as a service​

./gluo install installs a project command as a system service; it needs root, like any service file:

sudo ./gluo --env=prod install payment:sweep # systemd, runs it again every second
sudo ./gluo --env=prod install --template=crond payment:sweep # once a minute
sudo ./gluo --env=prod install --name=sweeper --description="Payment sweep" payment:sweep 24
sudo ./gluo uninstall payment:sweep

The service runs ./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 ./gluo list --check, and installing runs nothing.

Checking the commands​

./gluo list only lists. ./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 ./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.