Mini Framework - API Reference

Quick reference for Mini framework functions and classes.

Core Functions

Framework Bootstrap

bootstrap(): void       # Initialize framework (error handlers, output buffering)
router(): void          # Handle routing (calls bootstrap() internally)

Translation

t(string $text, array $vars = []): Translatable  # Create translatable text
translator(): Translator                          # Get translator instance

HTML & Output

h(string $str): string                            # HTML escape
render(string $template, array $vars = []): string  # Render template from _views/

Template Inheritance

Inside templates, $this provides helpers for layout inheritance:

$this->extend(string $layout)                          # Extend parent layout
$this->block(string $name, ?string $value = null)      # Define block (dual-use)
$this->end()                                           # End buffered block
$this->show(string $name, string $default = '')        # Output block with default

Dual-Use $this->block() Syntax:

// Inline: set block to value directly
$this->block('title', 'My Page');

// Buffered: capture block content
$this->block('content'); ?>
  <p>Content here</p>
<?php $this->end();

Example:

// Child template (child.php)
<?php $this->extend('layout.php'); ?>
<?php $this->block('title', 'My Page'); ?>
<?php $this->block('content'); ?><p>Page content</p><?php $this->end(); ?>

// Parent layout (layout.php)
<html><head><title><?php $this->show('title', 'Untitled'); ?></title></head>
<body><?php $this->show('content'); ?></body></html>

// Including sub-templates (partials)
<?= mini\render('_user-card.php', ['user' => $currentUser]) ?>

// Multi-level inheritance (3+ levels)
// base.php → layout.php → page.php

URL Generation

url(string $path = '', array $query = []): string  # Generate URL
redirect(string $url, int $statusCode = 302): void # Redirect
current_url(): string                              # Get current URL

Session & Flash Messages

session(): bool                               # Safe session initialization
flash_set(string $type, string $message): void  # Set flash message
flash_get(): array                             # Get and clear flash messages

Database

db(): DatabaseInterface  # Get request-scoped database instance

DatabaseInterface Methods:

query(string $sql, array $params = []): array          # All rows
queryOne(string $sql, array $params = []): ?array      # First row or null
queryField(string $sql, array $params = []): mixed     # First field of first row
queryColumn(string $sql, array $params = []): array    # First column as array
exec(string $sql, array $params = []): bool|int        # Execute (returns last insert ID or true)
lastInsertId(): ?string                                 # Get last insert ID
tableExists(string $tableName): bool                   # Check if table exists
transaction(\Closure $task): mixed                      # Run closure in transaction

Tables (Query Builder)

table(string $name): Repository  # Get table repository

Repository Methods:

eq(string $field, mixed $value): Repository       # WHERE field = value
gte(string $field, mixed $value): Repository      # WHERE field >= value
lte(string $field, mixed $value): Repository      # WHERE field <= value
gt(string $field, mixed $value): Repository       # WHERE field > value
lt(string $field, mixed $value): Repository       # WHERE field < value
in(string $field, array $values): Repository      # WHERE field IN (...)
like(string $field, string $pattern): Repository  # WHERE field LIKE pattern
order(string $field, string $direction = 'asc'): Repository  # ORDER BY
limit(int $limit): Repository                     # LIMIT
offset(int $offset): Repository                   # OFFSET
all(): array                                      # Fetch all results
first(): ?object                                  # Fetch first result
count(): int                                      # Count results
page(int $page, int $perPage = 20): array        # Paginated results

Cache

cache(?string $namespace = null): CacheInterface  # Get cache instance

CacheInterface Methods (PSR-16):

get(string $key, mixed $default = null): mixed
set(string $key, mixed $value, null|int $ttl = null): bool
delete(string $key): bool
clear(): bool
has(string $key): bool
getMultiple(iterable $keys, mixed $default = null): iterable
setMultiple(iterable $values, null|int $ttl = null): bool
deleteMultiple(iterable $keys): bool

Logging

log(): LoggerInterface  # Get PSR-3 logger instance

LoggerInterface Methods (PSR-3):

emergency(string $message, array $context = []): void
alert(string $message, array $context = []): void
critical(string $message, array $context = []): void
error(string $message, array $context = []): void
warning(string $message, array $context = []): void
notice(string $message, array $context = []): void
info(string $message, array $context = []): void
debug(string $message, array $context = []): void

Internationalization

fmt(): Fmt              # Get formatter instance

Fmt Static Methods:

Fmt::currency(float $amount, string $currencyCode): string
Fmt::dateShort(\DateTimeInterface $date): string
Fmt::dateLong(\DateTimeInterface $date): string
Fmt::timeShort(\DateTimeInterface $time): string
Fmt::dateTimeShort(\DateTimeInterface $dt): string
Fmt::dateTimeLong(\DateTimeInterface $dt): string
Fmt::number(float|int $number, int $decimals = 0): string
Fmt::percent(float $ratio, int $decimals = 0): string
Fmt::fileSize(int $bytes): string

Authentication

setupAuth(\Closure $factory): void  # Register auth implementation
auth(): ?AuthInterface              # Get auth instance
is_logged_in(): bool                # Check if user is authenticated
require_login(): void               # Require authentication (throws 401)
require_role(string $role): void    # Require specific role (throws 403)

AuthInterface (implement this):

interface AuthInterface {
    public function isAuthenticated(): bool;
    public function getUserId(): mixed;
    public function hasRole(string $role): bool;
}

CSRF Protection

csrf(string $action, string $fieldName = '__nonce__'): CSRF  # Create CSRF token

CSRF Class Methods:

$token = new CSRF('delete-post');           # Create token for action
$token = new CSRF('update-user', 'token');  # Custom field name

$token->getToken(): string                  # Get token string
$token->verify(?string $token, float $maxAge = 86400): bool  # Verify token
$token->__toString(): string                # Output hidden input field

Usage Example:

// Generate token
$nonce = csrf('delete-post');
echo render('form.php', ['nonce' => $nonce]);

// In template
<form method="post">
  <?= $nonce ?>
  <button>Delete</button>
</form>

// Verify token
$nonce = csrf('delete-post');
if ($nonce->verify($_POST['__nonce__'])) {
    // Process form
}

Security Features:

  • Tokens signed with HMAC-SHA256 using Mini::$mini->salt
  • Salt auto-generated from machine fingerprint + persistent random (zero-config)
  • Includes session ID and user agent for additional security
  • Time-based expiration (default 24 hours, customizable)
  • IP address validation
  • Self-contained tokens (no server-side storage needed)

Core Classes

Translatable

class Translatable implements \Stringable {
    public function getSourceText(): string
    public function getVars(): array
    public function getSourceFile(): ?string
    public function __toString(): string  # Returns translated text
}

Translator

class Translator {
    public function setLanguageCode(string $languageCode): void
    public function trySetLanguageCode(string $languageCode): bool
    public function getLanguageCode(): string
}

Mini (Container)

class Mini implements ContainerInterface {
    public static Mini $mini;                  # Global instance
    public readonly string $root;              # Project root
    public readonly PathsRegistry $paths;      # Path registries
    public readonly bool $debug;               # Debug mode
    public readonly string $locale;            # Default locale
    public readonly string $timezone;          # Default timezone
    public readonly string $defaultLanguage;   # Default language
    public readonly string $salt;              # Cryptographic salt (auto-generated or MINI_SALT)

    public function addService(string $id, Lifetime $lifetime, Closure $factory): void
    public function has(string $id): bool
    public function get(string $id): mixed
    public function loadConfig(string $filename, mixed $default = null): mixed
    public function loadServiceConfig(string $className, mixed $default = null): mixed
}

Lifetime Enum

enum Lifetime {
    case Singleton;   # One instance per application
    case Scoped;      # One instance per request
    case Transient;   # New instance every time
}

HTTP Exceptions

throw new Http\NotFoundException($message);        # 404
throw new Http\AccessDeniedException($message);    # 401/403
throw new Http\BadRequestException($message);      # 400
throw new Http\HttpException($code, $message);     # Custom code

Routing

File-Based Routes

Files in _routes/ directory map to URLs:

_routes/index.php              → /
_routes/users.php              → /users
_routes/api/posts.php          → /api/posts

Pattern Routes

In _config/routes.php:

return [
    "/users/{id:\d+}" => fn($id) => "_routes/users/detail.php?id={$id}",
    "/posts/{slug}" => fn($slug) => "_routes/posts/detail.php?slug={$slug}"
];

Directory Routes

In _routes/api/__DEFAULT__.php:

return [
    "/api/users" => fn() => "_routes/api/users.php",
    "/api/posts/{id}" => fn($id) => "_routes/api/posts/detail.php?id={$id}"
];

Configuration

Environment Variables

MINI_ROOT=/path/to/project      # Project root
MINI_CONFIG_ROOT=/path/config   # Config directory
MINI_ROUTES_ROOT=/path/routes   # Routes directory
MINI_VIEWS_ROOT=/path/views      # Views directory
MINI_LOCALE=nb_NO                # Default locale
MINI_TIMEZONE=Europe/Oslo        # Default timezone
MINI_LANG=nb                     # Default language
MINI_SALT=your-random-salt-here  # Cryptographic salt (optional, auto-generated if not set)
DEBUG=1                          # Debug mode

Config Files

All config files in _config/ directory:

  • bootstrap.php - Application initialization
  • routes.php - Pattern-based routes
  • PDO.php - PDO factory override
  • Psr/Log/LoggerInterface.php - Logger override
  • Psr/SimpleCache/CacheInterface.php - Cache override

CLI Commands

composer exec mini serve                         # Start development server
composer exec mini serve --host 0.0.0.0 --port 3000  # Custom host/port
composer exec mini migrations                    # Run pending migrations
composer exec mini translations                  # Validate translations
composer exec mini translations add-missing      # Add missing translation strings
composer exec mini translations add-language nb  # Create new language
composer exec mini translations remove-orphans   # Remove unused translations
composer exec mini benchmark                     # Run performance benchmarks

ICU MessageFormat Syntax

Plurals

t("{count, plural, =0{no items} =1{one item} other{# items}}", ['count' => 5])

Ordinals

t("{place, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}", ['place' => 21])

Select

t("{gender, select, male{He} female{She} other{They}}", ['gender' => 'male'])

Date/Time/Number Formatting

t("Today is {date, date, full}", ['date' => new DateTime()])
t("Price: {amount, number, currency}", ['amount' => 19.99])

Testing Helpers

function test(string $description, callable $test): void {
    try {
        $test();
        echo "✓ {$description}\n";
    } catch (\Exception $e) {
        echo "✗ {$description}\n";
        echo "  Error: {$e->getMessage()}\n";
    }
}

function assertEqual($expected, $actual, string $message = ''): void {
    if ($expected !== $actual) {
        throw new \Exception($message ?: "Expected != Actual");
    }
}

Native PHP Integrations

Mini uses native PHP directly where appropriate:

Request Data

$_GET['param']              # Query parameters
$_POST['field']             # Form data
$_FILES['upload']           # File uploads
$_SERVER['REQUEST_METHOD']  # HTTP method
$_SERVER['HTTP_*']          # Request headers
$_COOKIE['name']            # Cookies

Locale & Formatting

\Locale::setDefault('nb_NO')             # Set locale
\Locale::getDefault()                     # Get locale
date_default_timezone_set('Europe/Oslo')  # Set timezone
date_default_timezone_get()               # Get timezone

Intl Classes

$formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::CURRENCY);
$formatter = new \IntlDateFormatter(\Locale::getDefault(), ...);
$formatter = new \MessageFormatter(\Locale::getDefault(), $pattern);
$collator = new \Collator(\Locale::getDefault());

Service Override Pattern

Override framework services using config files:

// _config/Psr/Log/LoggerInterface.php
return new \Monolog\Logger('app', [
    new \Monolog\Handler\StreamHandler('php://stderr'),
]);
// _config/PDO.php
return new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');
// _config/mini/UUID/FactoryInterface.php
return new \mini\UUID\UUID4Factory();  // Use v4 instead of v7

See PATTERNS.md for detailed examples.

APCu Functions

Mini provides automatic APCu polyfills when the native extension is unavailable. All standard APCu functions work regardless of whether APCu is installed.

Basic Operations

apcu_store(string $key, mixed $value, int $ttl = 0): bool
apcu_fetch(string $key, bool &$success = null): mixed
apcu_exists(string|array $keys): bool|array
apcu_delete(string|array $key): bool|array
apcu_clear_cache(): bool

Atomic Operations

apcu_entry(string $key, callable $generator, int $ttl = 0): mixed  # Fetch-or-compute
apcu_add(string $key, mixed $value, int $ttl = 0): bool            # Store if not exists
apcu_cas(string $key, int $old, int $new): bool                    # Compare-and-swap
apcu_inc(string $key, int $step = 1, bool &$success = null, int $ttl = 0): int|false
apcu_dec(string $key, int $step = 1, bool &$success = null, int $ttl = 0): int|false

Information

apcu_cache_info(bool $limited = false): array|false  # Cache statistics
apcu_sma_info(bool $limited = false): array|false    # Shared memory info
apcu_key_info(string $key): ?array                   # Key metadata
apcu_enabled(): bool                                 # Check availability

Usage Examples

// Simple caching
apcu_store('config', $config, ttl: 300);
$config = apcu_fetch('config', $found);

// Fetch-or-compute pattern (atomic)
$data = apcu_entry('expensive:calculation', function() {
    return performExpensiveCalculation();
}, ttl: 60);

// Atomic counter
apcu_inc('page:views', 1);
$views = apcu_fetch('page:views');

// Conditional update
if (apcu_cas('counter', 5, 10)) {
    echo "Updated counter from 5 to 10";
}

Driver Selection

When native APCu is not installed, Mini automatically selects the best available driver:

  1. Swoole\Table - Coroutine-safe shared memory (requires Swoole)
  2. SQLite - Persistent storage in /dev/shm (requires pdo_sqlite)
  3. Array - Process-scoped fallback (no persistence)

Configuration (optional):

# .env
MINI_APCU_SQLITE_PATH=/custom/path.sqlite         # Custom SQLite path
MINI_APCU_SWOOLE_SIZE=4096                        # Swoole table size
MINI_APCU_SWOOLE_VALUE_SIZE=4096                  # Max value size

See README.md (APCu Polyfill section) for complete documentation.

Performance Tips

  1. Use APCu for L1 caching - Sub-millisecond operations via apcu_entry()
  2. Use direct SQL for simple queries - Skip the query builder when not needed
  3. Cache expensive operations - Use cache() for computed results
  4. Lazy initialization - Services only load when used
  5. File-based routing - No route compilation needed
  6. Request-scoped caching - Database, cache, logger instances reused within request

Common Patterns

Form Handling

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'] ?? '';
    $email = $_POST['email'] ?? '';

    // Validate, save, redirect
    db()->exec('INSERT INTO users (username, email) VALUES (?, ?)', [$username, $email]);
    redirect(url('users'));
}

echo render('form.php', ['title' => t('Create User')]);

API Endpoints

header('Content-Type: application/json');

try {
    $users = db()->query('SELECT * FROM users')->fetchAll();
    echo json_encode($users);
} catch (\Exception $e) {
    log()->error('Failed to fetch users', ['exception' => $e]);
    http_response_code(500);
    echo json_encode(['error' => 'Internal server error']);
}

Protected Routes

require_login();
require_role('admin');

$users = db()->query('SELECT * FROM users')->fetchAll();
echo render('templates/admin/users.php', ['users' => $users]);

See Also

  • README.md - Getting started and philosophy
  • PATTERNS.md - Advanced patterns (service overrides, middleware, response processing)
  • CLAUDE.md - Development guide for Claude Code