| Home | Getting Started | Core Concepts | Helpers | Extensions | Repo |
Tiny is a classical MVC framework with one quirk: there is no route table. The URL path maps directly to the filesystem (app/controllers/...), and from there it’s the familiar Model → Controller → View flow.
Request → Router → Middleware → Controller → Model → View → Response
app/controllers/)Controllers extend TinyController. Each HTTP verb is a method (get, post, put, patch, delete). See Controllers for the full reference.
<?php
class UserProfile extends TinyController
{
public function get($request, $response)
{
$user = tiny::model('user')->byId($request->path->slug);
$response->render('user/profile', ['user' => $user]);
}
public function patch($request, $response)
{
if (!$request->isValidCSRF()) {
return $response->hasCSRFError();
}
$data = $request->body(true);
if (tiny::model('user')->update($data)) {
tiny::flash('toast')->set(['level' => 'success', 'message' => 'Updated']);
return $response->redirect('/profile');
}
$response->render('user/profile', ['errors' => tiny::model('user')->validationErrors]);
}
}
app/models/)Models extend TinyModel and own data access + validation. Load them with tiny::model('user').
<?php
class UserModel extends TinyModel
{
public array $schemas = [
'account' => [
'name' => 'string(100)',
'email' => 'string(255)',
'active' => 'bool',
],
];
public function byId(int $id): ?array
{
return tiny::cache()->remember("user:$id", 60, function () use ($id) {
return tiny::db()->getOne('users', ['id' => $id]);
}) ?: null;
}
public function update(array $data): bool
{
if (!$this->isValid($data, $this->schemas['account'])) {
return false;
}
return (bool) tiny::db()->update('users', $data, ['id' => $data['id']]);
}
}
TinyModel::isValid() runs schema validation; the result is stored in $this->validationErrors. There’s also validationErrorsToAlpineJs() for one-shot Alpine.js error binding.
See Models for the validation grammar and more patterns.
app/views/)Views are plain PHP templates. They can use the global tiny::data() bag, the Component and Layout singletons, and any helper:
<?php Layout::default(['title' => 'Profile']); ?>
<h1><?= htmlspecialchars(tiny::get('user')['name']) ?></h1>
<?php if ($errors = tiny::get('errors')): ?>
<ul class="errors">
<?php foreach ($errors as $field => $msg): ?>
<li><?= htmlspecialchars($msg) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php Component::render('user-card', ['user' => tiny::get('user')]); ?>
See Views, Components, and Layouts.
html/index.php loads tiny/tiny.php, which initialises config, DB (if TINY_DB_AUTOCONNECT ≠ false), router, helpers, and middleware.handle() runs (web requests only). See Middleware.(TinyRequest $request, TinyResponse $response).$response->render(...), $response->sendJSON(...), or similar. Output is buffered (and optionally minified) before being sent to the client.tiny::cache()->remember() aggressively for hot reads.tiny::data() / tiny::set() / tiny::get() — that’s exactly what it’s for.