Build System
The build turns your annotated classes into generated PHP under src/System. It is
the bridge between Discovery and the typed runtime.
How a build runs
The build is the step between writing a class and being able to use it — it is steps two and five of the first feature walkthrough.
./framework build calls Builder::build(), which:
- loads the config and the Mustache
.mutemplates, - discovers every class implementing
DiscoveryBuilder(in the app and framework), - orders them by their
#[Priority], and - calls
generateCode()on each, writing the result intosrc/System.
./framework build # generate src/System
./framework destroy # remove the generated code
Writing a builder
A builder implements DiscoveryBuilder — a generateCode() that writes files and a
matching destroyCode() that removes them, both annotated #[\Override]. Collect data
from the discovered classes and render it through the Template enum, which knows every
.mu file. Each case exposes ->create($path, $data) and ->delete($path),
so you write the file yourself and can create the surrounding directory with Storage:
use Framework\Discovery\Type\DiscoveryBuilder;
use Framework\Discovery\Attr\Priority;
use Framework\Discovery\Package;
use Framework\System\Template;
use Framework\File\Storage;
#[Priority(Priority::Highest)]
class AgentPromptCode implements DiscoveryBuilder {
#[\Override]
public static function generateCode(): int {
$prompts = self::collectPrompts();
$typePath = Package::getSourcePath("Agent", "Type");
Storage::createDir($typePath);
// Renders AgentPromptCode.mu into $typePath/AgentPromptCode.php
Template::AgentPromptCode->create($typePath, [
"prompts" => array_keys($prompts),
]);
$total = count($prompts);
print("- Agent code -> $total prompts\n");
return 1;
}
#[\Override]
public static function destroyCode(): int {
$typePath = Package::getSourcePath("Agent", "Type");
$deleted = 0;
Template::AgentPromptCode->delete($typePath, $deleted);
Storage::deleteDir($typePath, $deleted);
return $deleted;
}
}generateCode() usually ends by printing a one-line summary of what it did and returning the number
of files it added; destroyCode() mirrors it, returning the number removed. Both can create and
remove their own sub-directories through Storage.
collectPrompts()) and let generateCode() only wire it to the template — the collector
is then trivial to unit-test. Note that Builder::generateCode() is a framework-internal shortcut
and is not used outside of it; apps render through the Template enum as above.Templates & the Template enum
Templates are Mustache
files ending in .mu. Every .mu file under src is picked up
automatically during a build and turned into a case on the generated Template enum, whose
name matches the file. A template is plain PHP with {{tags}} for the data you pass:
<?php
enum AgentPrompt {
{{#prompts}}
case {{.}};
{{/prompts}}
}Because the file is named AgentPromptCode.mu, the build exposes it as
Template::AgentPromptCode. Each case carries the same small set of methods:
| Method | Does |
|---|---|
render($data) | Renders the .mu file with your data and returns the PHP as a string. |
create($path, $data) | Renders and writes it as <Name>.php into $path; returns whether the file was written. |
delete($path, &$deleted) | Removes that file, adding to the $deleted counter — the inverse of create(). |
getFileName() | The output name, <Name>.php. |
use Framework\System\Template;
// Render to a string, or write straight to disk
$code = Template::AgentPromptCode->render(["prompts" => $prompts]);
Template::AgentPromptCode->create($typePath, ["prompts" => $prompts]);
// Remove it again (destroyCode)
$deleted = 0;
Template::AgentPromptCode->delete($typePath, $deleted);
Rendering aligns the doc-comment params; passing empty data returns an empty string, so
create() skips generation for that template.
src/System (and any sub-directories your builders
create) and are recreated on every build, so they should be git-ignored rather than committed.The builders that ship
Everything generated into src/System comes from a builder like the one above. They are worth
knowing by name, because a question about generated code is usually a question about one of them:
| Builder | Generates |
|---|---|
RouterCode | The Router — collects every #[Route] and writes the dispatch table. |
SignalCode | The Signal class — collects the listeners and merges their parameters. |
TemplateCode | The Template enum — one case per .mu file found. |
SchemaBuilder | The schema classes of every model. |
LanguageBuilder | The Language class, from the language files. |
ActionLogBuilder | The Sec and Act enums, from the log attributes. |
Configs | The Config class, from the .env files. |
AccessRole / SettingConfig | The Access and Setting classes. |
FilePath | The Path class. |
Each one is a DiscoveryBuilder found the same way your own would be, so nothing about them is
special — they are just the ones that come with the framework.
Watching for changes
Running build by hand after every edit gets old quickly.
Watcher backs the watch
command: it scans the app, and rebuilds whenever a file changes:
./framework watch
It respects .gitignore, polls every couple of seconds, and runs until you stop it — so a new route,
listener or model is available a moment after you save the file. It is the way to work while developing; CI and
deploys should run build explicitly.
What gets generated
A build writes one or more typed classes into src/System for every subsystem. Each is documented
where that subsystem is:
| Generated | What it is & where it's documented |
|---|---|
Router | The route table — Routing. |
Signal | The event dispatcher — Signals. |
Access | The access-role enum — Authentication. |
Config | Typed .env getters — Configuration. |
Setting | Typed database settings — Settings. |
Path | File-path helpers — Files & Storage. |
Template | The enum of every .mu file — above. |
Schema, Query, Entity, Column, Status | Per-model database classes — Database. |
Request | Per-model typed request — Model-backed routes. |
MediaSchema | Generated media models — Database. |
EmailCode | Localized email content — Emails. |
NotificationCode | Notification content — Notifications. |
Language | The NLS string accessor for your languages. |
Act, Sec, LogIntl | Action log types — see Logging. |