Code generation has an image problem, and it earned it. The classic scaffolder produces a wall of code you did not write, do not understand, and cannot safely modify — so you either keep it untouched and route around it, or you rewrite it and wonder why you generated it at all.
The failure is not generation. It is that the generated code doesn't look like the rest of the codebase. Tiknix's scaffold system is built on the opposite premise: the output's job is to be indistinguishable from something a careful developer on the project would have written.
What it generates
php cli/scaffold.php invoice model controller views
From a bean name, it produces:
- a FUSE model in
models/ - a CRUD controller, or an API controller, in
controls/ - index and edit views in
views/
The interesting part is where it gets the field list. It introspects the existing table. Because RedBeanPHP creates columns as you store beans, the normal flow is: write a few lines that store the object you have in mind, let the schema materialize, then scaffold the admin UI from the schema that actually exists.
That inverts the usual order in a useful way. You do not describe your data twice — once to a generator and once to the database. The database is the description.
Types map to widgets, and the map is a file
// lib/Scaffold/FieldRegistry.php
private array $typeWidgetMap = [
'string' => 'text', 'text' => 'textarea',
'int' => 'number', 'float' => 'number',
'bool' => 'checkbox', 'date' => 'date',
'datetime' => 'datetime', 'json' => 'json',
'email' => 'email', 'password' => 'password',
'url' => 'url', 'select' => 'select',
'enum' => 'enum',
];
Each widget is its own template in Templates/fields/ — thirteen of them, including a
fancyDateSelector that someone clearly added because the plain date input was not
good enough for a real screen.
That detail is the tell that this is a working tool rather than a demo. Generators that stay useful are the ones where "this widget is wrong for my case" has an answer that is a file rather than a fork.
The real product is the convention
Here is the part that matters more than the generator: generated code and hand-written code look the same, so reading one teaches you the other.
A scaffolded controller extends BaseControls\Control, uses $this->render(),
reads input through the request API, calls Bean:: for persistence, and relies on the
routing convention rather than registering routes. That is not because the generator is
opinionated — it is because every controller in the project does those things.
The consequences compound:
- You can edit generated code without a style shift, because it is already in the house style.
- You can read a scaffolded controller to learn how to write one by hand.
- An AI assistant reading your codebase sees one consistent pattern, not two dialects.
That last point deserves emphasis. Models are pattern-matchers. In a codebase with one clear convention, an agent's next file lands in the convention almost automatically. In a codebase with three eras of style, it produces a fourth. Consistency is not aesthetics — it is the input quality of every future generation.
Scaffold, then edit — and mean it
The workflow this system is designed for:
- Store a couple of beans so the table exists with the shape you want.
- Scaffold the model, controller, and views.
- Read the output. All of it. It is short.
- Edit it like your own code, because from this point it is.
There is no regeneration step, and that is deliberate. Generators that own their output forever — "don't edit below this line" — force you into their model of the world permanently. A generator that hands you a file and lets go gives you a fast start with no long-term constraint. The trade-off is real: you cannot re-run it to pick up an improved template. That is the correct trade for application code.
Where this fits with AI assistance
Scaffolding and an AI assistant are not competitors; they operate at different points on the determinism curve.
| Use the scaffolder for | Use an assistant for |
|---|---|
| The eleventh CRUD screen | Anything with domain logic |
| Output that must be byte-consistent with the last ten | Work that requires judgment |
| Structure you already know exactly | Structure you are still working out |
| Zero-review-needed boilerplate | Code you intend to review closely anyway |
A deterministic generator is strictly better than a model for work whose answer is already known — it is faster, free, and identical every time. Spending model tokens on the eleventh CRUD screen is spending judgment where no judgment is required.
And when the assistant does write a controller, the scaffolded ones around it are the examples it matches. The generator's real output is not files; it is a codebase where the pattern is unmistakable.
- Generated CRUD is a starting point, not a product. It gives you working list and edit screens. It does not give you the workflow, validation, or domain rules that make the feature worth using.
- Introspection reflects reality, including bad reality. Scaffold from a table with sloppy inferred types and you get a UI with sloppy inputs. Fix the schema first.
- No regeneration. Once you edit, you own it. Template improvements do not flow back into files you already generated.
- Permissions still need attention. A generated controller creates routes; those routes need
authcontrolrows. Build mode will create them permissively — review the levels before you ship. - Read what you generate. Same rule as inheriting a framework primitive: unreviewed code is unreviewed code regardless of who or what typed it.