AGENTS.md — Puko Framework Knowledge Base
This file is an LLM-friendly technical reference for the Puko Framework, a full-stack PHP framework for Rapid Application Development (RAD). It consolidates how the framework works so agents can write accurate documentation without re-discovering behavior.
- Framework source:
github.com/Velliz/puko(PHP, HMVC) - Official docs:
https://pukoframework.github.io(this repo, Jekyll) - PHP requirement: >= 7.0. Install via
composer create-project velliz/puko <project-name>.
1. Architecture (HMVC)
Puko uses the Hierarchical Model-View-Controller (HMVC) pattern.
| Layer | Location |
|---|---|
| Controllers | controller/ (PSR-4, namespace controller;) |
| Views (HTML) | assets/html/ (organized by language, then route path) |
| Models (generated wiring) | plugins/model/<schema>/ |
| Models (custom logic) | model/ |
Controller types (all PSR-4 namespaced)
Controllers must extend one of three base classes:
| Class | Purpose | Output |
|---|---|---|
View | Renders HTML via the PTE template engine | HTML |
Service | JSON APIs (requires ext-json) | JSON |
Console | CLI/background scripts (php cli <route> [arg]) | stdout |
Responses are exposed via the return keyword from a controller function.
2. Routing
Routing is managed exclusively via the Puko Console CLI (php puko routes ...). The generated config/routes.php file is read-only — never edit it manually.
php puko routes <clause> add <rest_style_urls>
php puko routes <clause> update <url>
php puko routes <clause> remove <url>
php puko routes service crud <schema>/<table> # full CRUD API scaffold
php puko routes list
- Clause:
view,service, orconsole. - URL params:
{?}(or?) marks dynamic PHPGETparameters, e.g.member/?/reports. - Path separators: URLs use forward slashes (
/), controller names use backslashes (\). Example:php puko routes view add accounts/user/{?}/links→ controllercontroller/accounts/user.php, functionlinks. - Generated files:
config/routes.php+controller/<path>.php. Forviewclauses it also generatesassets/html/en/<path>.htmlandassets/html/id/<path>.html. - Remove security note: Puko does NOT delete the
.php/.htmlfiles when removing a route — remove them manually.
3. Controllers
Namespacing & base classes
namespace controller; // or namespace controller\inventory;
class member extends View {} // or Service / Console
Responses
public function member() {
$data['Name'] = 'Didit Velliz';
$data['Address'] = 'Bandung';
return $data;
}
Doc Tags (annotation-style, prefix #)
Doc tags sit in PHP doc comments above a class or function. All must be prefixed with #; otherwise the framework skips them. Only tags in comments — function logic stays clean.
| Tag | Key | Value | Description |
|---|---|---|---|
#Value | name | Didit Velliz | Sends data directly to the view. |
#Template | master | true/false | Use the master layout. |
#Template | html | true/false | Output as HTML in the browser. |
#Template | cache | true/false | Cache HTML output with the PTE cache driver. |
#Date | before/after | d-m-Y H:i:s | Time-window access restriction (server time). |
#ClearOutput | binary | true/false | Bypass framework output processing. |
#Auth | bearer/session/cookies | true | Requires authentication. |
#Master | — | master-admin.html | Choose a non-default master layout. |
#DisplayException | — | true/false | Forward exceptions to View/Service middleware. |
#UnderConstruction | — | true/false | Show the “Under Construction” page. |
#Permission | \pukoframework\auth\Bearer@\plugins\auth\UserAuth | permissions@MANAGER | Role-based access; shows “Permission Denied” on failure. |
Example:
/**
* #Value Hobby Swimming
* #Auth bearer true
* #Master master-admin.html
*/
public function member() { ... }
Console controllers
- Generated via
php puko routes console add <route_name>(defaults toGETverb). - Example:
message/forgot/password/subscribe→ controllerconsole\forgot\password, methodsubscribe. - Run via
php cli message/forgot/password/subscribe <argument>(only one argument supported). - Benefits: no HTTP (more secure), no web-server timeouts, event-driven, message queues, WebSocket servers.
4. Views & PTE Template Engine
- View layer is presentation only — no business logic.
- Controllers wire a matching HTML file automatically (same directory structure as the controller). Generated by
php puko routes view add .... - Templates are pure HTML using PTE tags.
PTE tags
| Tag | Meaning |
|---|---|
{!x} | Print a value / variable. |
<!--{!x}--> … <!--{/x}--> | Loop over an array. |
{!fn()} / {!fn(x)} / {!fn(x,y,z)} | Function calls. |
{CONTENT} | Inject the content HTML into the master layout. |
{!css(<link ... />)} / {!js(<script ... ></script>)} | Declare assets in a content file. |
{!part(css)} / {!part(js)} | Render declared assets (placed in the master layout). |
{x.html} | Load an HTML segment/element file. |
{!url()} | Base URL helper, e.g. <a href="{!url()}user/profile">. |
- To exclude an asset, wrap it in an HTML comment inside the tag:
{!js(<!--<script src="..."></script>-->)}.
Master layouts
master.html(default) contains{CONTENT},{!part(css)},{!part(js)}.- Multiple masters supported (
master.html,master-admin.html,master-guest.html, …) for role-specific layouts; select via#Masterdoc tag (defaults tomaster.html).
Elements (modular view components)
- Self-contained packages in
plugins/elements/<name>/with<name>.php|html|js|css. - Manage via CLI:
php puko element add <name>(create) orphp puko element download <name>(fetch from the officialgithub.com/Velliz/elementsrepo). - Names must be alphanumeric/underscore only (
[a-z_A-Z]), no spaces or leading numbers. - Instantiate in controller, then render via the PTE tag:
$desc = new AdminLTE_Description('desc', []); $desc->SetStyle(AdminLTE_Description::HORIZONTAL); $data['desc'] = $desc;```html
---
## 5. Database Layer
### DBI (DataBase Interface) — singleton
```php
DBI::Prepare($sql)->GetData($params); // all rows, indexed array
DBI::Prepare($sql)->FirstRow($params); // single row
DBI::Prepare($sql)->Run($params); // UPDATE/DELETE/stored procs
- Prepared statements:
@1,@2positional params:DBI::Prepare("SELECT * FROM inventory WHERE id = @1 AND name = @2")->GetData($id, $name).
Data Objects (auto-generated)
- Generated by
php puko setup dbintoplugins/model/<schema>/— read-only. - Class extends
Model; mapped via doc tags: ```php /** - #Table tree_seeds
- #PrimaryKey id */ class tree_seeds extends Model { /** #Column id int(10) */ var $id = null; /** #Column price int(8) */ var $price = null; } ```
- Table names must be letters only (no special characters/spaces).
- CRUD:
- Create: set properties →
$obj->save(); - Read:
$obj = new plugins\model\primary\inventory(1); - Update:
$obj->modify(); - Delete:
$obj->remove(); - All rows:
inventoryContracts::GetAll();
- Create: set properties →
Model Contracts (generated static methods)
<Model>Contracts:: exposes: GetData(), GetById($id), IsExists($id), IsExistsWhere($column, $value), GetDataSize(), GetDataSizeWhere($condition = []), GetLastData(), SearchData($keyword = []), GetDataTable($condition = []).
For custom joins/stored procedures, extend the generated model and implement ModelContracts:
class InventoryModel extends inventory implements ModelContracts {
public static function SearchData($keyword = []) {
$strings = "";
foreach ($keyword as $column => $values) {
$strings .= sprintf(" AND (%s = '%s') ", $column, $values);
}
$sql = sprintf("SELECT i.id, i.created, i.name, i.descriptions
FROM inventory i
WHERE (i.created IS NOT NULL) %s;", $strings);
return DBI::Prepare($sql)->GetData();
}
}
Transactions
$transaction = DBI::Transactional('primary', function($dbi) use ($v) {
$v->save($dbi);
return true; // true = commit, false = roll back
});
$transaction evaluates to true/false.
Multiple databases
- Each connection has a unique schema label (default
primary). Set up extra connections viaphp puko setup db(prompts: database type, hostname, port, schema name, db name, user, pass). config/database.phpis an array keyed by schema label.- Use non-primary schemas by passing the label:
$object = new models(null, 'dashboard'); // insert $object = new models(20, 'dashboard'); // update $result = DBI::Prepare($sql, 'dashboard')->GetData(); - Supported engines (v1.1.6): MySQL, MariaDB, MSSQL. The setup wizard lists
mysql, oracle, sqlsrv, mongoas prompts.
6. Authentication & Roles
Setup
php puko setup auth <AuthName> # e.g. StudentAuth
Generates plugins/auth/StudentAuth.php with three boilerplate methods.
Login($username, $password)
Validate credentials (model, cURL, etc.), then must return a PukoAuth object:
public function Login($username, $password)
{
$student = model\primary\StudentModel::GetByUsernamePassword($username, $password);
$dataToSecure = ["id" => $student['id'], "username" => $student['user'], "class" => $student['class']];
$permissions = ["STUDENT"];
return new PukoAuth($dataToSecure, $permissions);
}
Invoking login
$login = Session::Get(StudentAuth::Instance())->Login($username, $password);
- Session/Cookies:
trueon success,falseon failure. - Bearer tokens: encrypted token string on success,
falseon failure.
Logout()
Cleanup callback; return true.
GetLoginData($secure, $permission)
Receives decoded $dataToSecure and the permission codes:
public function GetLoginData($secure, $permission)
{
return ["data" => $secure, "authorization" => $permission];
}
Protecting functions
/**
* #Auth session true
*/
public function create() {}
Permissions
- Permissions are string arrays attached at login:
$permissions = ["MANAGER"]; - Enforce with the
#Permissiondoc tag: ```php /** - #Auth session true
- #Permission \pukoframework\auth\Bearer@\plugins\auth\UserAuth permissions@MANAGER */ public function profile() ```
- Important: the
#Permissiontag requires the full PSR-4 path to the plugin auth class:\pukoframework\auth\Bearer@\plugins\auth\UserAuth.
7. Configuration
.env (never committed)
DB_* connection vars, ENVIRONMENT (DEVELOPMENT | STAGING | PROD | MAINTENANCE), ENCRYPTION_KEY. Start from .env.example.
config/app.php
Three root identifiers:
const— app-wide constants, read via$this->GetAppConstant('KEY')orConfig::Data().cache— Memcached connection (kind,expired,host,port).logs— Slack Incoming WebHooks error reporting (disabled by default; setactive: true).
config/database.php
Connection array keyed by schema label; managed via php puko setup db (or php puko refresh db to refresh without overwriting).
config/encryption.php
AES-256-CBC key protecting session/cookie/bearer auth data; set via php puko setup secure.
config/routes.php
Auto-managed route registry — read-only, manage via CLI.
Custom config files
Create e.g. config/rabbitmq.php; load via Config::Data('rabbitmq'):
$config = pukoframework\config\Config::Data('rabbitmq');
8. Internationalization (i18n)
- Language detection: client sends
X_LANGheader with a 2-letter ISO 639-1 code (en,id,jp, …). Defaults toidwhen missing. - Frontend: separate HTML per language under
assets/html/<lang>/— copies of templates must be maintained per language (performance-first, no runtime processing). - Backend:
$this->say('KEY', [$arg1, ...])readsassets/master/<lang>.master.json. Supportssprintf%splaceholders:{ "WRONG_ADDR": "The address for %s was not found for the email: %s" }throw new Exception($this->say('WRONG_ADDR', [$username, $email]));
9. Utilities
jQuery DataTables (server-side processing)
$table = new DataTables(DataTables::POST);
$table->SetColumnSpec(["name", "age", "address", "email"]);
$table->SetQuery("SELECT * FROM students;");
return $table->GetDataTables(function ($result) {
foreach ($result as $key => &$val) { /* transform */ }
return $result;
});
- JS
ajax.typemust bePOST; column count/order must matchSetColumnSpec. - Include assets via PTE:
{!js(<script src="assets/js/jquery.dataTables.min.js"></script>)}. - Recommend DataTables >= 1.10.
Pagination
$paginate = new Paginations();
$paginate->SetLength(10);
$paginate->SetQuery("SELECT * FROM students WHERE status = 'active'");
return $paginate->GetDataPaginations(function ($result) { ... });
- Requires
?page=(1-based) and?length=query params. - Response shape:
{page, totalpage, length, displayed, anchor[], totaldata, data[]}.
File uploads
- HTML form needs
POST+enctype="multipart/form-data". $file = Request::Files('filedata', null, true);(key, default,true= return object).- File object methods:
getName(),getType()(MIME),getTmpName(),getSize()(kB),isError(),isSizeSmallerThan($limit)(default 10MB),getFile(). - Save to DB (
LONGBLOBcolumn) via$model->filedata = $file->getFile();, or move to disk viamove_uploaded_file($file->getTmpName(), $uploadPath).
Framework helpers (inside View/Service/Console controllers)
| Helper | Description | | :— | :— | | $this->GetServerDateTime() | Current server time, Y-m-d H:i:s. | | $this->GetRandomToken($len = 6) | Random alphanumeric token. | | $this->GetAppConstant('KEY') | Value from config/app.php const section. | | $this->RedirectTo('login', false) | Redirect; true = replace history. | | $this->say('KEY', [$args]) | Localized string from assets/master. | | Framework::$factory->getBase() | Base URL (e.g. http://localhost:3000/). | | Framework::$factory->getRoot() | Absolute server path to project root. | | Framework::$factory->getEnvironment() | DEVELOPMENT, STAGING, or PROD. | | Framework::$factory->getStart() | PHP execution start microtime. |
Controller lifecycle hooks
public function BeforeInitialize() { /* before main function */ }
public function AfterInitialize() { /* after main function */ }
10. System States (Error Pages)
View-backed (extend View middleware)
controller/error.php+assets/html/id/error/display.html— triggered when the HTTPACCEPTheader doesn’t match the route’s registered verb.controller/error.php+assets/html/id/error/maintenance.html— shown whenENVIRONMENT = MAINTENANCE.controller/error.php+assets/html/id/error/notfound.html— non-existent route.
Doc-tag / exception driven (system templates, no controllers)
system/auth.html—#Auth truewith unauthenticated user.system/construction.html—#UnderConstruction true.system/error.html— internal error (e.g. undefined variable).system/exception.html— thrownException.system/permission.html—#Permissioncheck failed.
11. Puko Console (CLI) — Full Command Reference
php puko help # list commands
php puko version # console version
php puko setup db # DB wizard + generate model wiring
php puko refresh db # refresh DB config without overwriting
php puko setup secure # configure AES-256-CBC encryption
php puko setup auth <AuthName> # auth boilerplate in plugins/auth/
php puko routes <clause> add <url> # scaffold route
php puko routes <clause> update <url> # modify HTTP verbs via wizard
php puko routes <clause> remove <url> # delete route (files kept)
php puko routes service crud <schema>/<table> # full CRUD API
php puko routes list # list registered routes
php puko generate db # create tables from plugins/model schemas
php puko serve [port] # dev server (default 8080)
php puko tests # run tests in tests/unit/
php puko element add <name> # create a view element
php puko element download <name> # fetch element from official repo
php puko cli <route_path> # run a console controller
12. Project Structure (Puko app layout, as documented)
assets/
├── html/ # View templates by language (en/id) — matches route paths
├── master/ # Master layouts + localized string JSONs (<lang>.master.json)
└── system/ # System templates (auth, error, exception, permission, construction)
bootstrap/ # Docker/Nginx infrastructure configs
config/ # app.php, database.php, encryption.php, routes.php, custom files
controller/ # View/Service/Console controllers
model/ # Custom model contracts (extend generated models)
plugins/ # auth/, elements/, model/ (generated Data Object wiring)
tests/unit/ # Unit tests (run via `php puko tests`)
cli # Console entry point: php cli <route_path> [argument]
index.php # Web entry point
puko # Puko CLI dev tool
Generated content to treat as read-only: plugins/model/, config/routes.php, assets/html/<lang>/<route>.html.