Changelog - Version 7.0
Status: in development. This document tracks changes landing on the
7.0branch. Nothing here is released yet, and the contents may still change.
Breaking Changes
-
BaseConfigBootstrapno longer caches the resolved container forstagingandprod. It usedFileSystemCacheEnginein the system temp dir, which only works when that dir is thrown away on every deploy (e.g. a fresh container); on a cache that outlives the deploy (a shared memcached/Redis, a persistent volume) the new release kept loading the previous release's container. Where the cache lives now is the project's decision, inconfig/ConfigBootstrap.php; the starter template already does it. To keep the previous behaviour in an existing project:return new class extends BaseConfigBootstrap {#[\Override]protected function configureDefinition(Definition $definition): void{parent::configureDefinition($definition);$definition->getConfigObject('staging')->withCache(new FileSystemCacheEngine());$definition->getConfigObject('prod')->withCache(new FileSystemCacheEngine());}}; -
Generated tests are now in the
Tests\Controllernamespace (it wasTest\Controller), to match the other ByJG components. A project created before this change mapsTest\totests/in itscomposer.json: either rename that namespace toTests\(theautoload-deventry and thenamespace/uselines undertests/), or adjust the namespace of each newly generated test by hand.
New Features
-
ByJG\Gluo\Console\Launcher: the./gluocommand line entry point.Built-in commands replace the Composer scripts and run through the project's
Builder\Scripts, so project overrides still apply:Command Was ./gluo migrate:update(create,version,reset,up,down)composer migrate -- update./gluo codegen:all --table=x(model,repository,service,controller,test)composer codegen -- --table=x all./gluo openapi,./gluo openapi:checkcomposer openapi,composer openapi:check./gluo terminalcomposer terminalProject commands come from a
ByJG\Scriptify\CommandProviderclass of the project, which the./gluoscript passes to the launcher:new Launcher(__DIR__, new \App\Command\Commands()). Itscommands()maps a name to a target with class references:'user:cleanup' => Maintenance::class . '::cleanOldRecords', or a class for every public method it declares, in kebab-case ('payment' => PaymentCommands::classgivespayment:create,payment:clean-old-records, ...). A typo fails in the IDE and in Psalm, and a rename is refactor-safe. Arguments are positional and map to the method parameters;./gluo user:cleanup --helpprints the method's PHPDoc, and./gluo listits first line.The registry, the class expansion, the name rules, the help and the project rows of
list --checkare Scriptify'sLauncher;ByJG\Gluo\Console\Launcherextends it with the built-ins,--envand the database rows, and reserves the built-in names. The class of a project command is built by the Gluo container (Config::getContainer()), only when a project command runs orlist --checkaskshas():./gluo listand the built-ins never build it. -
./gluo --env=<env> install [--template=systemd|crond|upstart|initd] <command> [args...]installs a project command as a system service, and./gluo uninstall <command>removes it. The service runs./gluoitself, so the Gluo container is built when it starts, and its command line is written in full: the PHP binary, the absolute path to./gluo,--env, the command and its arguments, in the project root. The command must passlist --check. The service is named after the project directory and the command (shop-payment-sweep), or--name;--descriptionsets its description. Scriptify'sinstallCommand()writes it.--env=<env>(also--env <env>and-e <env>, before or after the command) is consumed by the launcher and exported asAPP_ENVbefore Config is initialised../gluo listshows both groups;./gluo list --checkadds a status per command (environment, configuration and database connectivity for the built-ins; name, target, visibility and container registration for project commands, without building them) and exits 1 if any is notOK../gluo migrate <sub>and./gluo codegen <components>without the colon are accepted so the Composer scripts can stay as aliases. -
BaseScripts::getMigrateHelp()andgetCodeGeneratorHelp()are now public, and their text uses the./gluosyntax. -
BaseLoginController::getResetEmailSubject()sets the subject of the password reset email, next to the existinggetResetEmailTemplate()hook. The subject used to be hard-coded as"Service - Password Reset", a leftover from the template's rename placeholder, which nothing could rename once the code moved intovendor/. The default is now"Password Reset"; override the hook to brand it:protected function getResetEmailSubject(): string{return 'Acme - Password Reset';} -
The generated REST controller (Repository pattern) receives its service through the constructor instead of calling
Config::get()in every method. The project'sServermust resolve controllers from the container: the starter does it withwithContainer()inconfig/dev/03-api.phpand anAutowirerule for the controller namespace inconfig/dev/07-controllers.php. Codegen writes no DI entry for controllers; repositories and services still get one; when04-repositories.php/05-services.phpdoes not exist, codegen prints a warning to register the class by hand. -
FakeApiRequestergives itsMockServerthe Gluo container (withContainer(Config::getContainer(), allowUnregistered: true)), so controllers with constructor dependencies work under test as they do in production. -
codegenemitsmaxLengthforcharandvarcharcolumns, on the model property and on the request body of the generated controllers. Templates get the declared length asfield.openapi_max_length(0for any other type).
Bug Fixes
-
The generated list endpoints documented
orderByandfilterquery parameters but only readpageandsize.orderByis now applied, through the newByJG\Gluo\Util\OrderBy::fromQueryString(): one or more fields separated by commas, each optionally followed byascordesc, accepted only when they are fields of the model (by property or column name) and rejected with a 400 otherwise, so nothing from the request reaches the SQL.filteris no longer generated: it had no defined format.BaseService::list()takes an optional third argument,$orderBy, passed to the repository. The generated test gainstestListOrderBy.Controllers generated before this change keep working and keep ignoring both parameters; regenerate the controller, or copy the list method from a new one.
-
BaseLoginController::postResetRequest()validated the request twice: once through the#[ValidateRequest]attribute on the route and again withOpenApiContext::validateRequest(). It now readsValidateRequest::getPayload(), likepost(), so the route must keep its#[ValidateRequest]attribute (the starter'sLoginControllerhas it). -
BaseRepository::getUuid()asked the database for the UUID withSELECT upper(uuid()), a MySQL-only function, so the password-reset flow ofBaseLoginControllerfailed on SQLite (and PostgreSQL) withno such function: uuid. It now generates a random version 4 UUID in PHP, in the same format (upper case, with dashes), and needs no database connection. -
codegenread the table with MySQL-only statements (EXPLAIN,SHOW INDEX,information_schema.key_column_usage), so it failed on any other database; on SQLite withnear "payment": syntax error. It now reads the table through the anydataset-db dialect (getTableMetadata(),getTableIndexes(),getTableForeignKeys()), on MySQL, SQLite, PostgreSQL, SQL Server and Oracle. Types such as PostgreSQL'scharacter varying(n),numericandtimestamp without time zoneare mapped like their MySQL counterparts.The template variables are unchanged, so custom templates keep working. On MySQL there are two differences:
field.keyisPRIor empty (it used to beUNIorMULfor other indexed columns), andfield.extraisauto_increment,VIRTUAL GENERATED(for any generated column, stored ones included, which are not writable either) or empty.indexesno longer has an entry for the primary key, which the templates skipped anyway.A table whose primary key is not auto-increment (a
binary(16)UUID) gets a model with micro-orm'sTableUuidPKAttribute, which generates the UUID in PHP on every database, instead ofTableMySqlUuidPKAttribute, which asked MySQL foruuid_to_bin(uuid())and is now deprecated.BaseScripts::buildCodegenData()now takes theColumnMetadata,IndexMetadataandForeignKeyMetadataobjects of anydataset-db instead of the raw MySQL rows; a project that overrides it has to follow. The newbuildCodegenDataFromDatabase()reads them. -
BaseApiTestCasereset the test database withMySqlDatabaseunlessgetDatabaseClass()was overridden, so the tests of a PostgreSQL or SQLite project failed withScheme "sqlite" does not found. It now registers every class of the newBaseScripts::MIGRATION_DATABASES(MySQL, PostgreSQL, SQLite), the list./gluo migratealready used, and the scheme ofDBDRIVER_CONNECTIONpicks one.getDatabaseClass()returnsnullby default; a project that overrides it keeps its class. -
./gluo list --checkmasks the database password in aDB_UNREACHABLEmessage with a literal replace, which corrupted the message when the password was short: a password200turnedSQLSTATE[HY000] [2002]intoSQLSTATE[HY000] [****2]. The literal replace now needs a password of at leastChecker::MASK_MIN_LENGTH(4) characters; a password inside a URL (://user:pw@) is masked whatever its length, including"0"and a password-only URL (://:pw@), which was left unmasked before.
Requirements
- PHP 8.3, 8.4, 8.5 and 8.6 are now supported:
"php": ">=8.3 <8.7". The previous<8.6upper bound excluded PHP 8.6, since<8.6is exclusive.
ByJG dependencies
byjg/anydataset-dbis now^7.0.byjg/authuseris now^7.0.byjg/cache-engineis now^7.0.byjg/configis now^7.0.byjg/jinja-phpis now^7.0.byjg/mailwrapperis now^7.0.byjg/micro-ormis now^7.0.byjg/migrationis now^7.0.byjg/restserveris now^7.0.byjg/scriptify^7.0is a new requirement: the launcher runs project commands through it.byjg/serializeris now^7.0.byjg/swagger-testis now^7.0.
While 7.0 is unreleased these resolve to 7.0.x-dev from each component's
7.0 branch, via minimum-stability: dev with prefer-stable: true.
Toolchain
-
PHPUnit updated to
^12.5. -
Psalm is installed as
psalm/pharinstead ofvimeo/psalm.vimeo/psalmlists the PHP versions it supports and no published release includes 8.6, so as a dev dependency it madecomposer installfail on the 8.6 build job before any test ran.psalm/pharrequires onlyphp ^8.2and bundles its own dependencies, so it installs on every PHP version in the matrix and cannot conflict with the project's. Psalm itself still refuses to run on 8.6, which is why the Psalm job uses 8.5.composer psalmruns it. -
PHPUnit 13 is deliberately not used. It requires PHP
>=8.4.1, breaking the 8.3 floor.
Continuous Integration
- The build matrix now includes PHP 8.6.
- The Psalm job runs on PHP 8.5.
Housekeeping
phpunit.xml.distrenamed tophpunit.xml.- Added the
LICENSEfile (MIT); the license was already declared incomposer.json.