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:

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

We use essential cookies to make this site work. With your permission we also use analytics to improve the site.