Tina4

Tina4 PHP - Quick Reference#

๐Ÿ”ฅ Hot Tips

  • Routes go in src/routes/, templates in src/templates/, static files in src/public/
  • GET routes are public by default; POST/PUT/PATCH/DELETE require a token
  • $response->json() returns JSON with the correct headers, no manual config
  • Run tina4 serve to start the dev server on port 7145

Installation โ€ข Static Websites โ€ข Routing โ€ข Middleware โ€ข Templates โ€ข Sessions โ€ข SCSS โ€ข Environments โ€ข Authentication โ€ข Forms & Tokens โ€ข AJAX โ€ข OpenAPI โ€ข Databases โ€ข Database Results โ€ข Migrations โ€ข ORM โ€ข CRUD โ€ข REST Client โ€ข Testing โ€ข Services โ€ข Websockets โ€ข Queues โ€ข WSDL โ€ข GraphQL โ€ข Localization โ€ข HTML Builder โ€ข Events โ€ข Logging โ€ข Response Cache โ€ข Health โ€ข DI Container โ€ข Error Overlay โ€ข Dev Admin โ€ข CLI โ€ข MCP Server โ€ข FakeData

Installation#

bash
# Install the tina4 CLI once. Windows: irm https://tina4.com/install.ps1 | iexcurl -fsSL https://tina4.com/install.sh | shโ€‹tina4 init php my-appcd my-apptina4 serve

More details on project setup and configuration options.

Static Websites#

Drop .html or .twig files in ./src/templates. Put assets in ./src/public.

twig
<!-- src/templates/index.twig --><h1>Hello Static World</h1>

More details on static website routing.

Basic Routing#

php
<?phpuse Tina4\Router;โ€‹Router::get("/", function ($request, $response) {    return $response->json(["message" => "Hello Tina4 PHP"]);});โ€‹// POST requires a formToken or Bearer authRouter::post("/api/items", function ($request, $response) {    return $response->json(["data" => $request->body], 201);});โ€‹// Dynamic path parametersRouter::get("/users/{id:int}", function ($request, $response) {    $id = $request->params["id"];    return $response->json(["user_id" => $id]);});

Follow the links for basic routing, dynamic routing with variables, and different response types.

Middleware#

The v3 middleware system uses class-based middleware with before*/after* static methods.

php
<?phpuse Tina4\Request;use Tina4\Response;โ€‹class AuthMiddleware{    public static function beforeAuth(Request $request, Response $response): array    {        if (!$request->bearerToken()) {            return [$request, $response->json(["error" => "Unauthorized"], 401)];        }        return [$request, $response];    }}

Register middleware globally or attach it to a single route.

php
<?phpuse Tina4\Middleware;use Tina4\Router;โ€‹// Global -- runs on every requestMiddleware::use(AuthMiddleware::class);โ€‹// Per-route -- third argumentRouter::get("/api/secret", function ($request, $response) {    return $response->json(["secret" => "The answer is 42"]);}, "requireApiKey");

Follow the links for more on middleware declaration, linking to routes, middleware chaining, and middleware with dynamic routes.

Template Rendering#

Templates live in ./src/templates. The framework uses Frond, a Twig-compatible engine built from scratch. Call $response->render() and pass your data.

twig
<!-- src/templates/hello.html --><h1>Hello {{ name }}</h1>
php
<?phpuse Tina4\Router;โ€‹Router::get("/", function ($request, $response) {    return $response->render("hello.html", ["name" => "World"]);});

Sessions#

Sessions start by default in the Auth constructor.

SCSS Stylesheets#

Drop files in ./src/scss. The framework compiles them to ./src/public/css.

scss
// src/scss/main.scss$primary: #2c3e50;body {  background: $primary;  color: white;}

More details on CSS and SCSS.

Environments#

The default development environment lives in .env.

[Project Settings]VERSION=1.0.0TINA4_DEBUG=trueTINA4_LOG_LEVEL=[TINA4_LOG_ALL]TINA4_CACHE_BACKEND=falseTINA4_DATABASE_URL=sqlite:///data/app.db[Open API]TINA4_SWAGGER_TITLE=Tina4 ProjectTINA4_SWAGGER_DESCRIPTION=Edit your .env file to change this descriptionTINA4_SWAGGER_VERSION=1.0.0

Access environment variables through the $_ENV superglobal.

php
$title = $_ENV["TINA4_SWAGGER_TITLE"];

Access env vars programmatically:

php
use Tina4\DotEnv;โ€‹DotEnv::loadEnv('.env');                     // Load .env file (auto on server start)DotEnv::getEnv('TINA4_DATABASE_URL');              // Get value or nullDotEnv::getEnv('PORT', '7146');              // Get value with defaultDotEnv::hasEnv('TINA4_DEBUG');               // true if setDotEnv::requireEnv('TINA4_DATABASE_URL');          // Throws if missingDotEnv::isTruthy(DotEnv::getEnv('TINA4_DEBUG'));  // true for "true", "1", "yes"

Authentication#

POST, PUT, PATCH, and DELETE routes are secured by default. GET routes stay public unless you mark them otherwise.

php
<?phpuse Tina4\Router;โ€‹// @secured marks a GET route as protected/** * @secured */Router::get("/api/profile", function ($request, $response) {    return $response->json(["user" => $request->user]);});โ€‹// Or use the chainable ->secure() methodRouter::get("/api/account", function ($request, $response) {    return $response->json(["account" => $request->user]);})->secure();

Generate and validate tokens with the Auth class.

php
use Tina4\Auth;โ€‹$token = Auth::getToken(["userId" => 1, "role" => "admin"]);$payload = Auth::validToken($token);

HTML Forms and Tokens#

Form tokens protect POST routes from cross-site forgery. Add one with a Twig filter.

twig
<form method="POST" action="/process-form">    {{ "emailForm" | formToken }}    <input name="email">    <button>Save</button></form>

The filter renders a hidden input with a signed JWT.

html
<form method="POST" action="/process-form">    <input type="hidden" name="formToken" value="ey...">    <input name="email">    <button>Save</button></form>

More details on posting form data, securing routes, Tina4 tokens, uploading files, handling errors, and a full login example.

AJAX and frond.js#

Tina4 ships with frond.js, a small zero-dependency JavaScript library for AJAX calls, form submissions, and real-time WebSocket connections.

More details on available features.

OpenAPI and Swagger UI#

Swagger is built in and lives at /swagger. The @description annotation registers a route in the documentation.

php
<?phpuse Tina4\Router;โ€‹/** * @description Returns all users */Router::get("/users", function ($request, $response) {    return $response->json((new User())->select("*"));});

Follow the links for more on configuration, annotations, and usage.

Databases#

Set TINA4_DATABASE_URL in .env. The framework reads it at startup and opens the connection.

bash
TINA4_DATABASE_URL=sqlite:///data/app.dbTINA4_DATABASE_URL=postgres://localhost:5432/myappTINA4_DATABASE_URL=mysql://localhost:3306/myappTINA4_DATABASE_URL=firebird://localhost:3050/path/to/database.fdb

Access the connection through the Database class.

php
<?phpuse Tina4\Database;โ€‹$db = Database::getConnection();$result = $db->fetch("SELECT * FROM products WHERE price > ?", [50]);

Follow the links for more on available connections, core methods, usage, examples, and transaction control.

Database Results#

Fetch a single row or a paginated set. The database returns a DatabaseResult object that converts to arrays or objects.

php
$db = Database::getConnection();โ€‹$row = $db->fetchOne("SELECT * FROM products WHERE id = 1");โ€‹// fetch($sql, $params, $noOfRecords, $offset)$result = $db->fetch("SELECT * FROM products ORDER BY name", [], 10, 0);

Dig into the usage guide and examples for deeper coverage.

Migrations#

The CLI manages migrations. Create one, add your SQL, then run it.

bash
tina4 migrate:create my-first-migration

Run all pending migrations in one command.

bash
tina4 migrate

More details on migrations, their creation, running them, and integration with ORM.

ORM#

Once your migrations have created the tables, ORM models map PHP classes to database rows.

php
<?phpuse Tina4\ORM;โ€‹class User extends ORM{    public string $tableName = "users";    public string $primaryKey = "id";โ€‹    public int $id;    public string $email;}โ€‹$user = new User(["email" => "alice@example.com"]);$user->save();$user = (new User())->load("id = ?", [1]);

ORM covers a lot of ground. Study the full reference to get the most from it.

CRUD#

One line of code generates a working CRUD system: screens, routes, and all.

php
(new User())->generateCrud("/my-crud-templates");

More details on how CRUD works and where it puts the generated files.

Consuming REST APIs#

Pull data from an external API in a single call.

php
$api = (new \Tina4\Api("https://api.example.com"))->sendRequest("/my-route", "GET");

More details on sending POST bodies, authorization headers, and other controls.

Inline Testing#

Tina4 lets you add tests to functions without setting up a test suite.

php
/** * @tests * assert(2, 5) == 7, "2+5 not equal 7" */public function addTwoNumbers($number1, $number2){    return $number1 + $number2;}

Run the tests from the CLI.

bash
tina4 test

Limitations and further reading on the testing system.

Services#

Create a process class. The service runner picks it up and executes it on schedule.

php
class MyProcess extends \Tina4\Process{    public function canRun(): bool    {        return true;    }โ€‹    public function run(): void    {        // Your work goes here    }}

Register the process with the service.

php
$service = new \Tina4\Service();$service->addProcess(new MyProcess("Unique Process Name"));

Further reading on services. Study them alongside threads for the full picture.

Websockets#

php
Router::websocket("/ws/chat/{room}", function ($connection, $event, $data) {    match ($event) {        "open" => $connection->send("Welcome to room " . $connection->params["room"]),        "message" => $connection->broadcast($data),        "close" => null,    };});

Queues#

Tina4 includes a queue system with multiple backends: LiteQueue (SQLite), MongoDB, RabbitMQ, and Kafka. Install tina4stack/tina4php-queue and use a single API for producing and consuming messages.

php
use Tina4\Queue;โ€‹$queue = new Queue(topic: 'my-events');$queue->produce('Hello World', userId: 'user123');โ€‹foreach ($queue->consume() as $message) {    echo $message->data;}

More details on queues, their backends, and configuration.

WSDL#

Define your WSDL service as a class. Tina4 handles the XML, the WSDL generation, and the SOAP envelope.

php
class Calculator extends \Tina4\WSDL {    protected array $returnSchemas = [        "Add" => ["Result" => "int"],    ];โ€‹    public function Add(int $a, int $b): array {        return ["Result" => $a + $b];    }}

Wire it to a route.

php
<?phpuse Tina4\Router;โ€‹Router::any("/calculator", function ($request, $response) {    $calculator = new Calculator($request);    $handle = $calculator->handle();    return $response->xml($handle);});

More details on WSDL services.

GraphQL#

php
use Tina4\GraphQL;โ€‹\$schema = <<<GQLtype Query {    hello(name: String!): String    users: [User]}โ€‹type User {    id: Int    name: String    email: String}GQL;โ€‹\$resolvers = [    "hello" => fn(\$args) => "Hello, " . \$args["name"] . "!",    "users" => fn() => \$db->fetch("SELECT * FROM users")->toArray(),];โ€‹\$graphql = new GraphQL(\$schema, \$resolvers);

Register the endpoint:

php
Router::post("/graphql", function (\$request, \$response) use (\$graphql) {    \$result = \$graphql->execute(\$request->body["query"] ?? "");    return \$response->json(\$result);})->noAuth();

GraphiQL UI available at /__dev/graphql in debug mode.

Localization (i18n)#

Set TINA4_LOCALE=en in .env. Place JSON files in src/locales/:

json
// src/locales/en.json{ "greeting": "Hello, {name}!" }
php
$i18n = new I18n();$i18n->t("greeting", ["name" => "Alice"]);  // "Hello, Alice!"โ€‹// Switch locale$i18n->setLocale("fr");

Back to top

HTML Builder#

php
$el = new HtmlElement("div", ["class" => "card"], ["Hello"]);echo $el; // <div class="card">Hello</div>โ€‹// Nesting$card = new HtmlElement("div", ["class" => "card"], [    new HtmlElement("h2", [], ["Title"]),    new HtmlElement("p", [], ["Content"]),]);โ€‹// Helper functionsextract(HtmlElement::helpers());echo $_div(["class" => "card"], $_h1("Title"), $_p("Description"));

Events#

Register listeners and fire them anywhere in the application.

php
use Tina4\Events;โ€‹// Register a listenerEvents::on("user.registered", function (array $payload): void {    echo "New user: " . $payload["email"];});โ€‹// Fire once -- listener is removed after first callEvents::once("app.boot", function (): void {    echo "Booted!";});โ€‹// Emit an eventEvents::emit("user.registered", ["email" => "alice@example.com"]);

Multiple listeners can bind to the same event. They run in registration order.

Logging#

Write log messages through the Debug class. The level is controlled by TINA4_LOG_LEVEL in .env.

php
use Tina4\Debug;โ€‹Debug::message("Starting import", TINA4_LOG_INFO);Debug::message("Query took 2.3s", TINA4_LOG_WARNING);Debug::message("Connection failed", TINA4_LOG_ERROR);
bash
# .envTINA4_LOG_LEVEL=TINA4_LOG_ALL    # DEBUG | INFO | WARNING | ERROR | NONE

Log output goes to src/logs/ by default. Set TINA4_LOG_DIR to change the destination.

Response Cache#

Chain ->cache() onto any route handler to store the response. Subsequent requests within the TTL skip the handler entirely.

php
use Tina4\Router;โ€‹Router::get("/api/products", function ($request, $response) {    $db = \Tina4\Database::getConnection();    return $response->json($db->fetch("SELECT * FROM products")->toArray());})->cache(300); // cache for 300 secondsโ€‹// Cache with a custom keyRouter::get("/api/report/{year}", function ($request, $response) {    return $response->json(buildReport($request->params["year"]));})->cache(3600, "report-" . date("Y"));

Set TINA4_CACHE_BACKEND=true in .env to activate caching globally.

Health Endpoint#

Tina4 exposes a built-in health check at /health. No setup required.

bash
curl http://localhost:7146/health
json
{  "status": "ok",  "uptime": 142,  "memory_mb": 12.4,  "php": "8.3.0",  "tina4": "3.10.20"}

Add custom checks by registering a health.check event listener.

php
use Tina4\Events;โ€‹Events::on("health.check", function (array &$report): void {    $report["db"] = \Tina4\Database::getConnection()->ping() ? "ok" : "fail";});

DI Container#

Register services once and resolve them anywhere. Singletons share one instance across the request lifecycle.

php
use Tina4\Container;โ€‹// Bind a factoryContainer::register("mailer", fn() => new Mailer($_ENV["TINA4_MAIL_HOST"]));โ€‹// Bind a singletonContainer::singleton("db", fn() => \Tina4\Database::getConnection());โ€‹// Resolve$mailer = Container::get("mailer");$db     = Container::get("db");

Route handlers receive resolved dependencies automatically when type-hinted parameter names match registered keys.

Error Overlay#

When TINA4_DEBUG=true, unhandled exceptions render an interactive overlay in the browser instead of a blank 500 page. The overlay shows the stack trace, request details, and the source file at the point of failure. No configuration needed, it activates automatically.

bash
# .envTINA4_DEBUG=true

In production set TINA4_DEBUG=false. Errors log to src/logs/ and return a plain 500 response.

Dev Admin#

Point a browser at /__dev while the dev server is running to open the admin dashboard.

bash
tina4 serve# open http://localhost:7146/__dev

The dashboard surfaces routes, registered middleware, environment variables, recent log entries, active queue workers, and the GraphiQL interface. It is disabled automatically when TINA4_DEBUG=false.

CLI Commands#

bash
# Scaffold a new projecttina4 init php my-projectโ€‹# Start the dev server (default port 7146)tina4 serveโ€‹# Start on a custom porttina4 serve --port 8080โ€‹# Run inline teststina4 testโ€‹# Create a migrationtina4 migrate:create add-users-tableโ€‹# Run all pending migrationstina4 migrateโ€‹# Rollback the last migrationtina4 migrate --rollbackโ€‹# Generate CRUD for a modeltina4 crud User

MCP Server#

Tina4 ships an MCP (Model Context Protocol) server that starts automatically when TINA4_DEBUG=true. AI assistants connect to it to inspect routes, models, and data without leaving the editor.

bash
# .envTINA4_DEBUG=trueTINA4_MCP_PORT=7147   # optional override, defaults to main port + 1
bash
# Verify the server is runningcurl http://localhost:7147/mcp/manifest

The manifest lists every route, ORM model, and migration. Disable it in production by setting TINA4_DEBUG=false.

FakeData#

Generate realistic test data without an internet connection. FakeData ships with Tina4, no extra packages required.

php
use Tina4\FakeData;โ€‹$fake = new FakeData();โ€‹echo $fake->name();          // "Alice Johnson"echo $fake->email();         // "alice.johnson@example.com"echo $fake->phone();         // "+1-555-0123"echo $fake->address();       // "42 Maple Street, Springfield"echo $fake->company();       // "Acme Corp"echo $fake->lorem(10);       // 10 words of placeholder textecho $fake->integer(1, 100); // random int between 1 and 100echo $fake->uuid();          // "f47ac10b-58cc-4372-a567-0e02b2c3d479"โ€‹// Seed for reproducible output$fake = new FakeData(seed: 42);

Use FakeData inside migrations, seeders, or test fixtures.


๐Ÿ“• Download the book#

Tina4 for PHP Developers (PDF), full reference, printable, with clickable table of contents and PDF outline. Regenerated with every release.