23: Implementatie & CI/CD
Een Phlo-app is statisch zodra deze is gebouwd. build::release transpileert je .phlo-bronnen naar gewone PHP, CSS en JS onder een release/-directory (zie het hoofdstuk Tooling); productie serveert die output zonder transpiler en zonder build-stap in het aanvraagpad. Deployment is de handeling van het veilig krijgen van een geteste release/ op de server en er veilig naar overschakelen.
Dit hoofdstuk beschrijft een pipeline die je kunt bouwen op elke CI-provider. De structuur is altijd hetzelfde: een push bouwt en test een release; een persoon keurt goed; het geteste artefact wordt verzonden; de server schakelt over en rolt terug bij een mislukte healthcheck. De mechanica is geautomatiseerd, de beslissing om vrij te geven blijft bij een mens.
23.1: The release build
The unit you deploy is the release/ tree, produced by:
php www/app.php build::release
It runs the release hooks, transpiles every .phlo to release/*.php, and writes the asset bundles to release/www/. Production's webroot points at release/www/ and the entrypoint runs with build: false and debug: false, so no build:: tooling and no control UI ship (see the Runtime config chapter).
The output is deterministic given the same sources and the same engine, so the release you test is the release you ship. Choose the engine policy deliberately. An independent or third-party app should normally pin a tag or exact commit for reproducible builds. A coordinated first-party fleet may track the current engine so all apps grow with Phlo, provided every engine change rebuilds and tests every app and the artifact records the resolved engine commit.
23.2: Continuous integration
Run CI on every push and pull request. A Phlo CI job does four things:
- Check out your app and the Phlo engine according to the chosen policy, and record both resolved commits.
- Install dependencies.
build::release, then lint the generated output:php -loverrelease/*.phpandnode --checkover the JS bundles. A release that does not lint never leaves CI.- Run the test suite (
phpunit), starting any service the tests need, such as a database.
On success, upload the release/ tree as a build artifact keyed by the commit. That artifact, not a fresh build on the server, is what deployment ships. Building once and shipping the exact bytes you tested takes "works in CI, breaks on the box" off the table.
# Sketch of the CI job. Adapt the syntax to your provider.
on: [push, pull_request]
jobs:
build:
steps:
- checkout app and engine (pinned, or tracked and tested as one fleet)
- install dependencies
- run: php www/app.php build::release
- run: for f in release/*.php; do php -l "$f"; done
- run: for f in release/www/*.js; do node --check "$f"; done
- run: vendor/bin/phpunit
- upload artifact: release/ (named after the commit)23.3: De deploypoort
Deployment is een aparte, handmatig geactiveerde taak (een workflow_dispatch op GitHub Actions, of het equivalente van jouw provider). De handmatige trigger is de goedkeuringsstap: CI bewijst dat een commit vrijgegeven kan worden, een persoon beslist wanneer het live gaat.
De deploy-taak bouwt niet opnieuw. Het lost een succesvolle CI-run op jouw release-tak op (de laatste, of een run-id die je doorgeeft), verifieert dat het echt een groene run op die tak is, downloadt het release-artifact en verzendt dat. Het gescheiden houden van build en deploy stelt je in staat om een bekend goed artifact opnieuw te implementeren of vooruit te rollen naar een specifieke geteste commit zonder dat een rebuild verrassingen introduceert.
23.4: Een veilige implementatie op de server
Verzend het artifact via SSH naar een enkele beperkte opdracht op de server; geef CI nooit een shell. Een minimale veilige ontvanger voor implementatie:
- Pakt de binnenkomende
release/uit in een stagingdirectory. - Maakt een snapshot van de momenteel live release (een tarball) voor rollback.
- Verwisselt de nieuwe release atomisch: schrijf naast de live tree, en verplaats het vervolgens, zodat een verzoek nooit een half-geüpdatete app ziet.
- Voert een healthcheck uit tegen de nieuwe release, een echt verzoek dat
200moet retourneren. - Bij een mislukte healthcheck herstelt het automatisch de snapshot en verlaat het met een niet-nul status.
Omdat de verwisseling atomisch is en de rollback automatisch, herstelt een slechte implementatie zichzelf naar de laatste goede release in plaats van de site offline te laten. Voer database-migraties uit als een expliciete, geordende stap vóór de verwisseling, en schrijf ze zodat ze veilig zijn terwijl de oude code de laatste momenten voor de switch bedient.
23.5: Recording what is live
CI writes a small build.json into the release artifact, recording the app commit, resolved engine commit and build timestamp. The receiver verifies it and copies it to data/build.json when that artifact goes live:
{ "commit": "<app-sha>", "engine": "<engine-sha>", "built": "<timestamp>" }
This is the source of truth for what is actually running, distinct from the app's own version prop. A dashboard or status page reads it to show a "deployed build" badge, so you can see which commit a server is on at a glance, without logging in.
23.6: Automatiseer de mechanica, niet de beslissing
Alles hierboven automatiseert de veilige delen: bouwen, testen, verzenden van identieke bytes, de atomische swap, de healthcheck, de rollback. Wat het opzettelijk aan een persoon overlaat, is de keuze om te releasen. Een push gaat niet live; iemand activeert de deploy wanneer de wijziging klaar is, nadat hij de diff heeft gelezen en weet wat er nog in de lucht is.
Die splitsing is het punt. Een release is een opzettelijke, inhoudsgerichte handeling: een schemawijziging, een eerste migratie, een robots regel, een asset cache-buster zijn beoordelingskeuzes, geen stappen die een git push op zichzelf zou moeten nemen. Automatiseer de repetitieve, foutgevoelige mechanismen zodat ze saai en omkeerbaar zijn, en houd de timing en de beoordeling bij een mens.
Laatst bijgewerkt op 07-08-2026