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
-
config/dev/03-api.phpbuilds the OpenAPISchemawithSchema::fromFile(). It usedSchema::getInstance(), which byjg/swagger-test 7.0 removes (deprecated since 6.0): a project created from an earlier template fails every API test withclass ByJG\ApiTools\Base\Schema does not have a method "getInstance"once it updates. Make the same change in the project:Schema::class => DI::bind(Schema::class)->withFactoryMethod('fromFile', [__DIR__ . '/../../public/docs/openapi.json'])->toSingleton(),fromFile()exists since swagger-test 6.0, so the change can be made before updating. -
byjg/swagger-test 7.0 also removes
assertRequest()(deprecated since 6.0), the former name ofsendRequest(). The template's tests already usesendRequest(); a project's own test that calls$this->assertRequest($request)must call$this->sendRequest($request). -
PUTtakes the key from the path:PUT /project/{id},PUT /task/{id}andPUT /note/{id}, likeGETandDELETE. Before, it wasPUT /projectwith the key in the body. The body is the same as thePOST's, the writable fields only; a key in the body is refused with 400. A client of the API, and a project's own controllers generated before this change, have to move. See gluo-core's changelog for the controller change, and use$this->service->update($request->attribute('id'), ValidateRequest::getPayload()):BaseService::update()now takes the key as its first argument.
Changes
-
A created project versions its
composer.lock. The template repository still leaves its own lock out (create-projectresolves the latest versions), but the installer removes that rule from the new project's.gitignore(the block# >>> template…# <<< template): an application commits its lock, so its builds and its Docker image install what was tested. -
The routes and the spec are compiled into
generated/.php gluo openapialso writesgenerated/routes.php(the routing table, loaded by byjg/restserver'sCompiledRouteList) andgenerated/openapi.php(the spec as a PHP array, read by#[ValidateRequest]), andopenapi:checkfails when they are out of date: commit them withopenapi.json.config/dev/03-api.phpbindsRouteListInterfacetoCompiledRouteListandSchematogenerated/openapi.php. OPcache keeps them in memory, so a request no longer parsesopenapi.json(~390 us for the routes, ~160 us for the validation) nor builds the dispatcher. The route cache (OpenApiRouteList::withCache()) is gone; how the routes are compiled (the output processors) is inbuilder/Scripts.php,getOpenApiRouteList(). The Docker image copiesgenerated/. -
The container is cached as PHP files.
config/ConfigBootstrap.phpcachesstagingandprodwith byjg/cache-engine'sPhpFileCacheEngine(system temp dir, files0600) instead ofFileSystemCacheEngine: OPcache keeps the cached container in memory, ~300 us less per request. -
public/app.phpuses gluo-core'sApplication. It was a classAppdeclaring what to handle; it is nowConfig::get(Application::class)->handle();, withApplicationbound inconfig/dev/03-api.php.Application::handle()also takes a PSR-7 request, so a persistent worker (RoadRunner, Swoole, a CLI worker) can get theApplicationonce and serve every request with it. An existing project updatespublic/app.php, binds the routes asRouteListInterface::class(it wasOpenApiRouteList::class; theApplicationand the API tests get them by that key) and adds the binding:Application::class => DI::bind(Application::class)->withInjectedConstructor()->toSingleton(), -
The agent skill is
gluo-developer, published instead of copied. It wasdev-guide, copied into every new project, where it stayed as it was on the day the project was created. The source is still.claude/skills/gluo-developer/in this repository; it is published with the documentation, at https://opensource.byjg.com/docs/ai/agent-skills/gluo-developer, and in the ByJG plugin catalog (byjg/skills).create-projectnow removes.claude/skills/and writes a.claude/settings.jsonthat declares the catalog and enablesgluo-developer@byjg, so Claude Code offers the skill once the project folder is trusted. Other agents install it withnpx skills add byjg/skills --skill gluo-developer. A project created before this change can delete its.claude/skills/dev-guide/, add the same.claude/settings.json, and renamedev-guidetogluo-developerin its.claude/CLAUDE.md. -
The database server is the
gluo-databaseservice, indocker-compose.database.yml. It wasmysql-container(postgres-containerfor PostgreSQL) indocker-compose.yml, with fixedcontainer_names (resttest,mysql-container) that made every project created from the template claim the same names: a second project could not start while another one's containers existed, even stopped. Nowdocker-compose.ymlholds the API and the frontend and includesdocker-compose.database.yml, which holds the server of the database chosen (none for SQLite). Compose names the containers after the project (<project>-rest-1). Usedocker compose exec rest ...instead ofdocker exec resttest .... -
No
/etc/hostsentry for the database.config/devandconfig/testconnected tomysql-container, which the commands run on the host (php gluo,php gluo test) only reached with127.0.0.1 mysql-containerin/etc/hosts. They now connect to127.0.0.1, the published port, and therestservice setsDBDRIVER_CONNECTIONtogluo-databasefor the app in the container. The tests always use the test database:tests/bootstrap.phpkeeps the server of aDBDRIVER_CONNECTIONset in the environment and replaces its database with the test one (it ignores a SQLite one: the test file ofconfig/testapplies), so they run on the host, in therestcontainer (docker compose exec rest php gluo test) or in a CI job that sets the variable, and never on the dev database. -
composer create-projectno longer creates a git repository. The installer rangit initand made an "Initial commit", so a project created inside an existing repository (a monorepo, a demo with several apps) got a nested.gitto remove by hand. Rungit inityourself when the project is a repository of its own. The installer no longer asks for the git user name and email, and no longer needs git;git_user_nameandgit_user_emailinsetup.jsonare ignored. -
composer create-projectno longer leaves the starter's changelogs in the new project.CHANGELOG-*.mdare the history of this starter, not of the project being created, so the installer removes them, as it already did withdocs/. -
?orderBy=works on the list endpoints, and?filter=is gone. The example controllers (Project,Task,Note) documented both parameters and applied neither.orderByis now applied throughByJG\Gluo\Util\OrderBy::fromQueryString(), which only accepts fields of the model (?orderBy=name desc,id; anything else is a 400).filterwas removed from the OpenAPI attributes because it never had a defined format. See Filtering and Ordering. -
The frontend is published over HTTPS too.
docker-compose.ymlnow maps7443:8443on thefrontendservice, so the SPA answers on https://localhost:7443 with the self-signed certificatebyjg/static-httpservergenerates at start. HTTP on:7080is unchanged. See Frontend. -
php gluois the command-line entry point. It replaces the Composer scripts for the framework tasks and runs your own commands by a short name:New Was php gluo --env=dev migrate:updatecomposer migrate -- --env=dev updatephp gluo --env=dev codegen:all --table=product --savecomposer codegen -- --env=dev --table=product all --savephp gluo openapicomposer openapiphp gluo terminalcomposer terminalphp gluo testcomposer testphp gluo user:cleanup 30scriptify run "\\MyApp\\Service\\MaintenanceService::cleanOldRecords" --arg 30The Composer scripts remain as aliases that call
php gluo, so existing pipelines keep working. Project commands are declared in the newsrc/Command/Commands.php, aByJG\Scriptify\CommandProviderthat thegluoscript passes to the launcher, with class references (PaymentCommands::class) instead of strings. They run with the Gluo container, so a command's class gets its constructor dependencies.php gluo list --checkvalidates every command without running it, andphp gluo --env=prod install <command>installs one as a system service. See The gluo Command.Builder\Scriptslost its static Composer entry points (migrate,codeGenerator,genOpenApiDocs,checkOpenApi); therun*methods the launcher calls are unchanged. -
The config cache is set in
config/ConfigBootstrap.php.byjg/gluo-coreno longer caches the resolved container for staging and prod; the template now does it inconfigureDefinition(), with the sameFileSystemCacheEngineas before, so the behaviour doesn't change. Change it there if your deploy needs another cache (see Environment-Specific Caching). -
composer create-projectno longer copiesdocs/into the new project. The copy was frozen at creation time whilebyjg/gluo-corekeeps updating throughcomposer update, so it drifted out of date. The project now gets a shortREADME.mdwith a quick start, the common commands and links to the online documentation at https://opensource.byjg.com/docs/php/gluo/. The.claude/skills are still copied. -
The test namespace is
Tests\, notTest\.composer.jsonmapsTests\totests/, the classes undertests/and the documentation follow, andbyjg/gluo-coregenerates new tests inTests\Controller. A project created from an earlier template keeps working as it is; rename its namespace when you want the generated tests to match. -
The example lists page and sort. The projects screen pages (
page,size) and sorts by name through the API, with a newPaginationcomponent infrontend/src/components/ui.jsx. It used to show only the first 50 projects. -
GET /project/{projectId}/taskreturns a project's tasks, filtered by the database (TaskService::getByProjectId()). The project screen used to load the first 50 tasks of every project and filter them in the browser, so tasks disappeared once there were more. -
The examples are what codegen writes now:
maxLengthand, for required text,minLength: 1onproject.name,task.titleandnote.body, and a test per entity that sends invalid bodies to the server. -
public/docs/openapi.jsonis OpenAPI 3.1. -
Every save is validated against the model's OpenAPI schema, not only requests.
Project,TaskandNotedeclarevalidator: SchemaValidator::classin their table attribute, so asave()from a command, a queue consumer or a service is held to the same rules as#[ValidateRequest]and fails with the same 400. Their keys, and Note's computedbodyLengthanddays, are markedreadOnly: the server generates them. See OpenAPI Integration. -
Noteuses Gluo'sActiveRecordtrait, and its controller callsNote::getOrFail($id)instead ofNote::get($id)followed by a null check, inGET,PUTandDELETE. A missing record is a 404 named after the model in both patterns:"Note not found","Project not found"(it was"Id not found"in the Repository pattern). See Traits.
Bug Fixes
-
The Docker image copied the
gluoscript andcomposer.json"for the migration script", but notdb/, sophp gluo migrate:updatein the image had no migrations to run. It now copiesdb/(a local SQLite database file in it stays out, through.dockerignore). -
A project created with
db_schemasqliteorpostgresdid not work: only the connection string changed. Now:- the project gets the SQL and the server of its database. The template keeps them in
.db-dialects/<mysql|postgres|sqlite>/(db/anddocker-compose.yml); the installer writes the set chosen todb/anddocker-compose.database.ymland removes.db-dialects/, so a project keeps onedb/base.sql, onedb/migrations/and one database server (none for SQLite); - a SQLite database is a file under
db/,sqlite://{__ROOT__}/db/<name>.sqlite, which works on the host and in Docker (it wassqlite:///<name>, a file at the filesystem root), and*.sqliteis ignored by git; - the staging and prod placeholder connections use the scheme of the database chosen;
- the questions use the user of the database chosen (
root,postgres), and SQLite is not asked for a host, user or password.
The installer offers
mysql,postgresandsqlite.sqlsrvis no longer offered: a project created with it could not run its migrations. The create-project CI job runs every database. This needs, from the libraries: byjg/anydataset-db (thepostgres://alias), byjg/migration (postgres://migrations), byjg/micro-orm (an empty key and "now" on PostgreSQL), byjg/authuser (user properties on PostgreSQL) and byjg/gluo-core (BaseApiTestCasepicking the database). - the project gets the SQL and the server of its database. The template keeps them in
-
An unattended setup that could not read its answers fell back to the questions, and so created the project with the default answers: a
SETUP_JSONnaming a file that is not there (a Composer running in a container sees only the directories mounted into it), or asetup.jsonthat is not valid JSON. Both now stop the setup with an error naming the file. -
A Composer running in a container (a dockerised
composer) warned that Docker is not installed and waited for ENTER, because the container has nodockercommand. Inside a container the setup now says Docker could not be checked, and goes on. -
A new project kept the starter's own
composer.jsondescription ("Runcomposer create-project byjg/gluo…", with the project's name in it). It is now "A PHP REST API built with Gluo." -
The installer rewrites
byjg/gluoto the project's composer name wherever the template names itself, and some comments about the framework saidbyjg/gluowhere they meantbyjg/gluo-core: a new project gotBaseUser (acme/shop)insrc/Model/User.php, and the same inLoginController.php. Those comments now namebyjg/gluo-core, which the rewrite leaves alone. -
src/Generator/UuidSeedGenerator.phpis removed. Nothing in the template used it, and it ran the MySQL-onlySELECT hex(uuid_to_bin(uuid())).TableUuidPKAttributegenerates UUID keys; for a seed function of your own, returnHexUuidLiteral::forDatabase($executor, HexUuidLiteral::v7()). -
composer create-projectwrote the package name twice when the chosen name started withbyjg/gluo(byjg/gluo-shopbecamebyjg/gluo-shop-shopincomposer.json). The installer now replaces only the template's exact name. Names with any other vendor or prefix were never affected. -
UserandTaskused micro-orm'sTableMySqlUuidPKAttribute, which asked MySQL foruuid_to_bin(uuid()), so every insert intousersortaskfailed on any other database. They now useTableUuidPKAttribute, which generates a version 7 UUID in PHP. A project created from an earlier template keeps working on MySQL; replace the attribute to run it elsewhere. -
The installer accepted only PHP 8.3, 8.4 and 8.5, although the project supports 8.6. It now accepts
8.6too (interactive prompt andphp_versioninsetup.json). -
A Composer running where there is no Node (a dockerised
composer, the create-project CI job) ended with a warning that the frontend dependencies were NOT installed, although the frontend image installs them whendocker compose upbuilds it. Without npm/bun the installer now prints a note instead; the warning stays for npm/bun being there and failing. -
http://localhost:8080/docsredirected tohttp://localhost/docs/, without the port: nginx in the container listens on 80 and does not know the published 8080. The redirect is now relative (absolute_redirect off), to/docs/on the same host and port.
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. - The Docker image is based on
byjg/php:8.5-fpm-nginx-2026.10(was2026.06). The installer replaces the PHP version in that tag, and2026.06was never published for 8.6;2026.10exists for 8.3, 8.4, 8.5 and 8.6.
ByJG dependencies
byjg/gluo-coreis now^7.0.byjg/scriptifyis 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
- In this repository,
docker-compose.yml,docker-compose.database.yml,db/base.sqlanddb/migrations/are no longer versioned:php gluo --env=dev db:dialect <mysql|postgres|sqlite>writes them from.db-dialects/, the only place to change them. The command and.db-dialects/are removed from a created project. The CI runs the command before the tests and before building the image. phpunit.xml.distrenamed tophpunit.xml..vscode/launch.jsonis removed, and.vscodeis ignored by git.