12: Advanced
Phlo stays deliberately modular. You can keep an app small and activate only the resources it needs, or combine multiple source paths and resource groups.
12.1: App code and runtime resources
App-specific code belongs in the app itself. Do not put it in /srv/phlo/resources/.
/srv/phlo/resources/ is the Phlo runtime catalog: framework-wide resources that may be shared across multiple apps and are deliberately maintained alongside the runtime. Only generic, stable code belongs there.
If you want to share code between apps, first create an explicit shared module or app library path with a clear owner. Only promote code to the Phlo runtime catalog when it is truly framework functionality.
A runtime resource can provide an object, function, style or script. Metadata at the top of the file helps the Phlo Control Center and the manual:
@ summary: Send app notifications
@ package: notifications
@ frontend: false
@ backend: true
method send($message){
return HTTP(%creds->notify->url, POST: ['message' => $message])
}12.2: Multiple source paths
Keep the default simple: app source in the app path. Only add extra paths when a codebase genuinely needs to be shared.
Path choices should stay predictable:
- app source in
/srv/example.nl/ - runtime in
/srv/phlo/ - release in
/srv/example.nl/release/ - data and credentials in
/srv/example.nl/data/
12.3: Integrating with existing PHP
Use Phlo alongside existing PHP by loading the runtime and making the Phlo entrypoint responsible only for the routes the app handles. Existing static files keep being served directly by the webserver.
<?php
require('/srv/phlo/phlo.php');
phlo_app (
id: 'Legacy',
host: 'dev.legacy.test',
build: true,
debug: true,
app: '/srv/legacy/',
);12.4: Security and visitors
For public sites, the usual baseline is:
{
"resources": [
"cookies",
"security/security",
"security/token",
"session",
"useragent",
"visitors",
"phlo.async",
"DOM/form"
]
}
For local dev you can exclude tracking:
{
"exclude": [
"visitors",
"useragent",
"wsCast"
]
}12.5: Cookiewall: GDPR consent
DOM/cookiewall is a built-in, subtle consent banner. Activate it in 3 steps:
1. Resource in data/app.json:
{ "resources": [..., "DOM/cookiewall"] }
2. Banner in your layout:
view layout:
<body>
{{ %cookiewall->banner }}
<main>...</main>
</body>
The banner only appears when the visitor hasn't made a choice yet. Two buttons: "Essential only" and "Accept". The choice is stored in a cookie cookieChoice ('essential' or 'all'), valid for 1 year.
3. Guard around tracking:
<if %cookiewall->canTrack>
<script src="https://analytics.example.com/script.js"></script>
</if>
| Method | Returns |
|---|---|
%cookiewall->hasChosen() |
true once the visitor has chosen something |
%cookiewall->canTrack |
true only for the 'all' choice |
%cookiewall->canAnalytics |
Alias of canTrack, semantically useful for an analytics bridge |
%cookiewall->choice |
'essential' / 'all' / null |
Multilingual variant: DOM/cookiewall.translated (uses the {nl: ...} shorthand for the banner texts).
12.6: Worker mode
By default Phlo runs per request: PHP process starts, handles the request, process ends. With thread: true in phlo_app(...), the runtime stays in memory between requests, intended for FrankenPHP, ReactPHP or RoadRunner.
The performance gain is large (no boot per request), but three rules apply:
1. No die() or exit() in the HTTP path. Both kill the entire worker, not just the current request. Use return or let a terminating call (view(), apply(), location()) send the response.
2. No request state in static properties. Statics survive between requests. Data from request A leaks into request B. Statics are only safe for class structure or computed metadata that is identical for all requests, not for session, user, payload, time or DB state.
3. Mark long-lived objects with $objPers = true. By default Phlo clears its instance map between requests. For objects you explicitly want to reuse (DB connection, prepared statements), set $this->objPers = true so the cleanup leaves them alone.
Combining with build: true is not allowed: build writes files between requests, and in a worker that is a race condition. Phlo throws a runtime error if you enable both.
12.7: Modifying resources without forking
Sometimes you want a shared resource to behave just slightly differently in one app, without copying or changing that resource. From any .phlo file, you can inject or override a node in a different class by naming the node as %<class>.<node>:
static %visitors.table = 'control.visitors'
prop %visitors.db = 'control'
method %model.greet => 'hi'
The first line overrides the static $table of the visitors model; the second adds a db prop to visitors; the third adds a greet method to model. During the build, the compiler strips the %<class>. prefix and writes the node into <class>: an existing node with that name is overwritten, a new one is added. The target class must be part of the build (its resource loaded), otherwise the modifier is silently ignored. Keep the node type identical to what you replace (static with static, prop with prop): the entire node is swapped.
A practical example: have the shared visitors model write to a central analytics database, while all other queries in the app stay on the app's own connection:
static %visitors.table = 'control.visitors'
This keeps the shared resource agnostic while every app gives it its own interpretation.
12.8: File metadata: the complete @ reference
Every .phlo file can open with @ key: value lines. Any key is stored as file metadata; these have engine or tooling meaning:
| Key | Effect |
|---|---|
@ class: |
Override the PHP class name |
@ extends: |
PHP inheritance (default: obj) |
@ implements: |
PHP interfaces, comma-separated |
@ use: |
PHP use statement (Full\Name as Alias) |
@ namespace: |
PHP namespace |
@ type: |
class (default), abstract class, interface or trait |
@ summary: |
One-line description, shown in the manual, reflection and the Phlo Control Center |
@ package: |
Group name for tooling |
@ frontend: / @ backend: |
Marks a resource frontend- or backend-only |
@ requires: |
Dependencies, resolved when the resource is enabled; name? is optional, php-ext: and creds: entries are informational |
@ provides: / @ binds: |
Frontend APIs offered / selectors hooked; feeds reflect::selectorGraph |
@ tags: |
Free-form labels, shown in reflection indexes |
@ advice: |
Developer guidance, shown in reflect::objectIndex |
12.9: Best practices
- Keep entrypoints explicit; avoid hidden configuration.
- Let release output come from
build::release. - Never put credentials in source files.
- Use reflection to verify resources, routes and functions.
- Add abstraction only when multiple apps genuinely benefit from it.