Skip to content

FullStuck.php v0.2.0 Cheatsheet โ€‹

Zero-config, single-file PHP micro-framework. No Composer. No vendor/. Deploy anywhere.

๐Ÿ“š Full Documentation & Tutorials: https://github.com/stuckfull/fullstuck/blob/main/docs/v0.2.0.md


Quick Start โ€‹

AI Agent setup:

https://raw.githubusercontent.com/stuckfull/fullstuck/main/docs/ai-setup.md

CLI init (headless):

bash
php fullstuck.php init --db=sqlite --admin-pass=stuck --admin-url=/stuck --spa=yes --scaffold=yes --htaccess=yes
# --db: sqlite | mysql | pgsql | none
# --spa: yes | no
# --scaffold: yes | minimal | no

Dev server (GUI wizard):

bash
php -S localhost:8000 fullstuck.php

Web server config:

apache
# Apache .htaccess
RewriteEngine On
RewriteBase /
RewriteRule ^(.*)$ fullstuck.php [L]
nginx
# Nginx
location / { try_files $uri $uri/ /fullstuck.php?$query_string; }
caddyfile
# **FrankenPHP / Caddy** (Caddyfile):
:8000 {
    root * .
    php_server {
        index fullstuck.php
    }
}

Project Structure โ€‹

my-project/
โ”œโ”€โ”€ assets/          # Static files (CSS, JS, images)
โ”œโ”€โ”€ fst-plugins/     # Plugins โ€” manage via Admin Dashboard ONLY
โ”œโ”€โ”€ views/           # HTML/PHP templates
โ”œโ”€โ”€ fullstuck.json   # Config (single source of truth)
โ”œโ”€โ”€ fullstuck.php    # Framework core โ€” DO NOT MODIFY
โ””โ”€โ”€ router.php       # Route definitions

fullstuck.json โ€” Config Reference โ€‹

json
{
    "environment": "development",
    "ai_sop": true,
    "admin": {
        "page_url": "/stuck",
        "password": "$2y$12$...",
        "allowed_ips": []
    },
    "database": {
        "default": "main",
        "connections": {
            "main": { "driver": "sqlite", "database_path": "database.sqlite" },
            "mysql_db": {
                "driver": "mysql",
                "host": "${DB_HOST}",
                "dbname": "my_database",
                "username": "${DB_USER}",
                "password": "${DB_PASS}"
            }
        }
    },
    "routing": {
        "base_path": "/",
        "require": ["models", "utils.php", "helpers/api_*.php"],
        "public_folders": ["assets", "uploads", "storage/public"],
        "routes_file": ["router.php"],
        "error_handlers": {
            "404": "views/errors/404.php",
            "403": "Sorry, you do not have permission.",
            "500": "views/errors/500.php"
        },
        "regex_shortcuts": {
            "i": "([0-9]+)",
            "a": "([a-zA-Z0-9]+)",
            "s": "([a-zA-Z0-9\\-]+)",
            "h": "([a-fA-F0-9]+)",
            "any": "([^/]+)"
        }
    },
    "spa": {
        "enabled": true,
        "default_target": "body",
        "header_request": "X-FST-Request",
        "header_target": "X-FST-Target",
        "indicator_class": "fst-loading",
        "history_cache": false
    }
}

Key notes:

  • environment: "development" shows full error traces; "production" hides them (logged to .fst-error.log).
  • ai_sop: true = AI follows guided phase SOP; false = freestyle mode.
  • ${ENV_VAR} syntax interpolates OS environment variables โ€” no .env file needed.
  • routing.require: auto-includes PHP files/folders/globs before routes execute.
  • spa.enabled: true (global), false (off), or "manual" (opt-in per page via fst_spa_page()).
  • spa.default_target: change from "body" to "#app" if you have persistent sidebar/player.
  • spa.history_cache: false (default) = back/forward always re-fetches from server. true = replay cached HTML (instant but stale).

Strict Rules for AI โ€‹

These rules are mandatory. Do not deviate.

  1. Use fst_* helpers only. Never use $_POST, $_GET, $_FILES raw or new PDO().
  2. Never modify fullstuck.php.
  3. Never manually edit files in fst-plugins/. Use the Admin Dashboard.
  4. Always call fst_csrf_check() at the top of POST/PUT/DELETE route callbacks.
  5. Validate input with fst_validate() only.

Admin Dashboard (/stuck) โ€‹

Built-in control panel:

  • Config Editor, Route Viewer
  • Plugin Manager (install from GitHub Store)
  • File Integrity Monitor, 1-Click Update

Production hardening: change the URL and set allowed_ips in fullstuck.json.


Core Concepts โ€‹

Routing โ€‹

php
fst_get('/hello', fn() => print "Hello!");
fst_get('/user/{id:i}', fn($id) => print "ID: $id");        // :i = integer
fst_get('/post/{slug:any}?', fn($slug = 'home') => ...);    // ? = optional
fst_post('/submit', fn() => ...);
fst_any('/catch-all', fn() => ...);

Middleware (onion model):

php
function require_login($next) {
    if (!fst_session_get('user_id')) return fst_redirect('/login');
    return $next();
}

fst_group('/admin', function() {
    fst_get('/dashboard', fn() => print "Admin Area");
}, 'require_login');

Database โ€‹

Query Builder โ€” supports AND + = conditions only. For OR, LIKE, IN, >, etc., use fst_db().

php
// SELECT
$users  = fst_db_select('users', ['status' => 'active'], ['order_by' => 'id DESC']);
$user   = fst_db_row('users', ['email' => 'a@b.com']);         // single row or null
$exists = fst_db_exists('users', ['email' => 'a@b.com']);      // bool

// INSERT / UPDATE / DELETE
$id = fst_db_insert('users', ['name' => 'Budi', 'email' => 'budi@a.com']);
fst_db_update('users', ['status' => 'inactive'], ['id' => 5]);
fst_db_delete('users', ['id' => 5]);

// Multi-connection
fst_db_select('customers', [], ['connection' => 'mysql_db']);

Raw Query fst_db($mode, $sql, $params, $connection):

ModeReturns
'ALL'array of associative arrays
'ROW'single associative array
'SCALAR'single primitive value
'EXEC'['affected_rows', 'last_insert_id']
php
// JOIN
$posts = fst_db('ALL', "SELECT p.*, u.name FROM posts p JOIN users u ON p.user_id = u.id WHERE p.status = ?", ['published']);

// OR / LIKE
$products = fst_db('ALL', "SELECT * FROM products WHERE (name LIKE ? OR description LIKE ?) AND status = ?", ['%shoe%', '%shoe%', 'active']);

// IN
$cats = [1, 2, 5];
$placeholders = implode(',', array_fill(0, count($cats), '?'));
$items = fst_db('ALL', "SELECT * FROM items WHERE category_id IN ($placeholders)", $cats);

// SCALAR
$total = fst_db('SCALAR', "SELECT COUNT(*) FROM users WHERE status = ?", ['active']);

// EXEC
fst_db('EXEC', "UPDATE users SET last_login = NOW() WHERE id = ?", [1]);

Transactions:

php
try {
    fst_db_begin();
    fst_db_insert('orders', [...]);
    fst_db_update('stock', ['qty' => 0], ['id' => 1]);
    fst_db_commit();
} catch (Exception $e) {
    fst_db_rollback();
}

Request, Validation & CSRF โ€‹

CSRF โ€” mandatory on all mutating routes:

php
fst_post('/register', function() {
    fst_csrf_check(); // MUST be first line

    $val = fst_validate(fst_request(), [
        'name'  => 'required|min:3',
        'email' => 'required|email',
        'age'   => 'required|min_value:18|max_value:60'
    ]);

    if (!$val['valid']) {
        fst_flash_set('error', implode(', ', array_merge(...array_values($val['errors']))));
        return fst_redirect('/register');
    }

    $clean = $val['data']; // trimmed & sanitized input
});

Validation rules: required, email, numeric, in:a,b, min:X, max:X, min_value:X, max_value:X. For unique or regex, do manual checks after fst_validate().

CSRF in HTML templates:

php
// .php view
<?= fst_csrf_field() ?>

// fst_template ruleset (for .html files)
"form" => ["@append" => 'fst_csrf_field()']

Flash messages (PRG pattern):

php
// Set (before redirect)
fst_flash_set('success', 'Saved!');

// Get (in next GET request)
$msg = fst_flash_get('success'); // null after first read

Views โ€‹

PHP views:

php
// router.php
fst_view('profile.php', ['name' => 'Budi', 'age' => 25]);

// views/profile.php
<p>Name: <?= e($name) ?></p>  <!-- e() = XSS escape, always use for user data -->

Layout nesting:

php
// router.php
fst_view('layout.php', ['view_path' => 'content.php', 'view_data' => ['title' => 'Hello']]);

// views/layout.php
<main><?php fst_view($view_path, $view_data); ?></main>

Shared global variables (available in all views):

php
fst_view_share('site_name', 'MyApp');
fst_view_share(['user_role' => 'admin', 'theme' => 'dark']);
// fst_view_share vars are auto-injected into fst_template too

File Upload โ€‹

php
fst_post('/upload', function() {
    fst_csrf_check();

    $result = fst_upload('photo', 'assets/uploads', [
        'max_size'      => 2048,        // KB
        'allowed_types' => ['jpg', 'png'],
        'allowed_mimes' => ['image/jpeg', 'image/png']
    ]);

    // Single file: $result = ['success', 'path', 'error', 'original_name']
    // Multiple (name="photo[]"): $result = array of the above
});

Procedural DOM Templating (fst_template) โ€‹

HTML files stay 100% static โ€” no PHP tags. Logic is declared as a PHP ruleset. Auto-escapes text by default (XSS-safe).

Signature:

php
fst_template(string $html_path, array $data, array $rules, ?string $cache_dir = null, bool $force_rebuild = false);

In development, pass fst_is_dev() as $force_rebuild for hot-reloading.

How Rules Work โ€‹

Rules are string PHP expressions evaluated at render time. Always wrap in single quotes so they're not executed at definition time:

php
"selector" => '$variable'         // โœ… evaluated at render
"selector" => "$variable"         // โŒ evaluated immediately (undefined var error)

Ruleset DSL โ€” Full Reference โ€‹

php
$rules = [

    // --- TEXT & HTML ---

    "title"        => '$pageTitle',                    // set innerText (XSS-safe)
    "h3"           => ["@text" => '$heading'],         // explicit text (use when mixing with attrs)
    "span.content" => ["@html" => '$htmlContent'],     // raw innerHTML (trusted content only)
    "head"         => ["@append"  => '"<style>...</style>"'],  // insertAdjacentHTML beforeend
    "div.wrap"     => ["@prepend" => '"<div class=\"alert\">!</div>"'],  // afterbegin

    // โš ๏ธ @append/@prepend render raw HTML โ€” escape user data manually:
    // "div.comment" => ["@append" => '"<p>" . fst_escape($userComment) . "</p>"']


    // --- ATTRIBUTES ---

    "a.external"   => ["[href]" => '$linkUrl', "[target]" => '"_blank"'],
    "[data-fst=\"my-form\"]" => ["[action]" => '"/submit"', "h2" => '"Login"'],
    "a[data-type='link']"    => '"New Link Text"',    // CSS attribute selector as key


    // --- SINGLE NODE (prefix ^ = querySelector, not querySelectorAll) ---

    "^div.alert"   => '"First alert only"',


    // --- COMPILE-TIME REMOVAL (permanent, runs before cache is written) ---

    "div.debug-panel" => "@remove",                   // removes element entirely
    "img.thumbnail"   => ["[style]" => "@remove", "[src]" => '$realUrl'],  // removes attribute


    // --- CONDITIONALS & LOOPS (runtime PHP logic) ---

    // @if โ€” show/hide element
    "div.promo"       => ["@if" => '$isPromoActive'],
    "a.btn-dashboard" => ["@if" => '$isLoggedIn'],
    "a.btn-login"     => ["@if" => '!$isLoggedIn'],

    // Ternary โ€” change content/attrs on same element
    "button.auth"     => [
        "@text"   => '$isLoggedIn ? "Logout" : "Login"',
        "[href]"  => '$isLoggedIn ? "/logout" : "/login"',
        "[class]" => '$isLoggedIn ? "btn-danger" : "btn-primary"'
    ],

    // @foreach โ€” first child node = template, extra dummy nodes auto-removed
    "ul.nav > li"     => [
        "@foreach" => '$menus as $menu',
        "a"        => ["[href]" => '$menu["url"]', "@text" => '$menu["label"]']
    ],

];

Selector scoping: Avoid generic selectors like span or h2 โ€” they match globally. Prefer #content span.price to avoid unintentional overwrites.

Cache: Auto-invalidates when the HTML file or ruleset (MD5 hash) changes. Cache files are in view-cache/ โ€” inspect them if you get a ParseError.

Use cases: SEO meta tags, dark mode hydration, injecting json_encode() into <script type="application/json">.


SPA (Single Page Application) โ€‹

SPA is on by default โ€” all <a> clicks and <form> submits are intercepted and fetched in the background.

HTML Data Attributes โ€‹

AttributeEffect
data-fst-target="#id"Update only this element instead of default_target
data-fst-indicator="class"Override indicator_class for this element only
data-fst-history="false"Don't push to browser history (use on delete/action forms)
data-fst-no-spa or class no-spaForce full page reload (logout, file downloads)
data-fst-ignore (on <script>)Don't re-execute this script on SPA navigation
data-fst-scroll="instant|smooth|false"Scroll behavior after load
data-fst-cache="true|false"Per-link back/forward cache control (overrides global history_cache)
html
<!-- Fragment update -->
<a href="/tab-profile" data-fst-target="#content">Profile</a>
<form action="/search" method="GET" data-fst-target=".results" data-fst-scroll="false">...</form>

<!-- No history pollution for action forms -->
<form action="/task/delete" method="POST" data-fst-history="false">...</form>

<!-- Force hard reload -->
<a href="/logout" data-fst-no-spa>Logout</a>

Scroll Behavior โ€‹

  • Default on new route: scroll to top (instant)
  • Back/Forward: re-fetches from server by default (ensures fresh data + security). Scroll position is auto-restored after load
  • Set "history_cache": true in config or data-fst-cache="true" per-link to replay cached HTML instead (instant but may be stale)
  • Anchor links (#section): handled natively by browser; cross-page anchors (/about#team) load page then smooth-scroll to target

Lifecycle Events โ€‹

javascript
// cancelable โ€” call e.preventDefault() to abort navigation (e.g., dirty form check)
document.addEventListener('fst:loading', (e) => {
    // e.detail: { url, targetSelector, triggerElement }
    if (window.isFormDirty && !confirm('Unsaved changes. Leave?')) e.preventDefault();
    NProgress.start();
});

// fires after fetch, before DOM injection โ€” destroy old plugins here
document.addEventListener('fst:unload', () => {
    $('.select').select2('destroy');
});

// fires after DOM injection and scripts re-run โ€” re-init plugins here
document.addEventListener('fst:load', () => {
    NProgress.done();
    $('.select').select2();
});

Programmatic Navigation โ€‹

javascript
// Basic
fst.go('/dashboard');

// With options
fst.go('/profile', {
    target:    '#main-content',   // CSS selector
    history:   false,             // don't push to browser history
    scroll:    'smooth',          // 'instant' | 'smooth' | false
    indicator: 'loading-class',   // CSS class for loading state
    cache:     true               // cache this page for back/forward (overrides global)
});

API Reference โ€‹

Routing & Response โ€‹

php
fst_get|post|put|patch|delete|any($path, $callback, $middleware)
fst_group($prefix, $callback, $middleware)

fst_view($path, $data)                          // render PHP view โ†’ void
fst_partial($path, $data)                       // alias for small components โ†’ void
fst_template($path, $data, $rules, $cache?, $force?) // render HTML template โ†’ void
fst_view_share($key, $value)                    // share var to all views โ†’ void

fst_json($data, $status = 200)                  // send JSON โ†’ exit
fst_text($string, $status = 200)                // send plain text โ†’ exit
fst_redirect($url, $code = 302, $external = false) // redirect (sends X-FST-Redirect in SPA) โ†’ exit
fst_abort($code, $message)                      // HTTP error response โ†’ exit

fst_uri()                                       // โ†’ string: current path
fst_method()                                    // โ†’ string: HTTP method
fst_input($key, $default = null)                // โ†’ mixed: single GET/POST/JSON value
fst_request()                                   // โ†’ array: all input
fst_file($key)                                  // โ†’ array|null: $_FILES entry
fst_upload($key, $folder, $options)             // โ†’ ['success','path','error','original_name'] or array[]
fst_is_spa()                                    // โ†’ bool
fst_spa_target()                                // โ†’ string|null: CSS selector from SPA agent
fst_spa_page()                                  // manually activate SPA injection for current page
fst_status_code($code)                          // set response status code โ†’ void
fst_serve_static_file($path)                    // serve file with cache headers โ†’ bool

Database โ€‹

php
fst_db($mode, $sql, $params = [], $connection = null)
// mode: 'ALL' โ†’ array[], 'ROW' โ†’ array, 'SCALAR' โ†’ mixed, 'EXEC' โ†’ ['affected_rows','last_insert_id']

fst_db_select($table, $cond = [], $opts = [])   // opts: select, limit, offset, order_by, mode, connection โ†’ array
fst_db_row($table, $cond = [], $opts = [])      // โ†’ array|null
fst_db_exists($table, $cond = [], $opts = [])   // โ†’ bool
fst_db_insert($table, $data, $opts = [])        // โ†’ last insert ID or bool
fst_db_update($table, $data, $cond, $opts = []) // โ†’ int (affected rows)
fst_db_delete($table, $cond, $opts = [])        // โ†’ int (deleted rows)

fst_db_begin($connection = null)                // โ†’ bool
fst_db_commit($connection = null)               // โ†’ bool
fst_db_rollback($connection = null)             // โ†’ bool
fst_db_quote_ident($ident, $connection = null)  // safe table/column quoting โ†’ string

Security, Validation & Session โ€‹

php
e($str) | fst_escape($str)                      // XSS-safe HTML escape โ†’ string (always use when echoing user data)
fst_csrf_field()                                // โ†’ string: <input type="hidden" name="_token" value="...">
fst_csrf_token()                                // โ†’ string: raw CSRF token
fst_csrf_check()                                // validate CSRF โ€” call at top of POST/PUT/DELETE handlers

fst_validate($data, $rules)
// โ†’ ['valid' => bool, 'errors' => ['field' => ['msg']], 'data' => array (sanitized)]
// rules: required | email | numeric | in:a,b | min:X | max:X | min_value:X | max_value:X

fst_session_set($key, $val)
fst_session_get($key)                           // โ†’ mixed
fst_session_forget($key)

fst_flash_set($key, $val)
fst_flash_get($key)                             // โ†’ mixed (cleared after first read)
fst_flash_has($key)                             // โ†’ bool

Config & Utilities โ€‹

php
fst_config($key, $default = null)               // read from fullstuck.json โ†’ mixed
fst_is_dev()                                    // โ†’ bool: is development mode?
fst_is_safe_to_debug()                          // โ†’ bool: are error traces visible to user?
fst_app($key, $value = null)                    // internal state container (request lifecycle cache) โ†’ mixed
fst_register_plugin($id, $config)               // register plugin โ†’ void
fst_dump(...$vars)                              // pretty var dump โ†’ void
fst_dd(...$vars)                                // pretty var dump + die โ†’ void (exit)

Dibuat dengan โค๏ธ untuk ekosistem PHP yang lebih sederhana.