tina4 3.8.69

Tina4 — Unified CLI for Python, PHP, Ruby, and Node.js frameworks
<?php
/**
 * Swoole entry point for a Tina4 PHP app. Generated by
 * `tina4 deploy docker --runtime swoole`.
 *
 * App::__invoke() is the seam: it takes a Swoole\Http\Request and returns a
 * Tina4\Response. Routes, ORM, middleware and Frond are untouched - only the
 * transport changes.
 */

require __DIR__ . '/vendor/autoload.php';

$app = new \Tina4\App(__DIR__);

$host = getenv('TINA4_SWOOLE_HOST') ?: '0.0.0.0';
$port = (int)(getenv('TINA4_SWOOLE_PORT') ?: 7145);

$http = new Swoole\Http\Server($host, $port);

/**
 * How many workers, on whichever extension is actually installed.
 *
 * OpenSwoole 22+ REMOVED the procedural swoole_*() aliases: on openswoole
 * 26.2.0, swoole_cpu_num() and swoole_version() are both gone and the CPU count
 * lives on OpenSwoole\Util. Mainline Swoole is the mirror image - it has the
 * functions and no OpenSwoole\Util. Calling either one unguarded is a fatal at
 * STARTUP on the other build, which is the worst place for it: the image builds
 * fine, then the container exits 255 the moment it runs.
 *
 * (Measured: an earlier version of this file called swoole_cpu_num() directly
 * and did exactly that.)
 */
$workers = (int)(getenv('TINA4_SWOOLE_WORKERS') ?: 0);
if ($workers <= 0) {
    if (class_exists('OpenSwoole\Util')) {
        $workers = \OpenSwoole\Util::getCPUNum() * 2;
    } elseif (function_exists('swoole_cpu_num')) {
        $workers = swoole_cpu_num() * 2;
    } else {
        $workers = 4;
    }
}

$http->set([
    'worker_num' => $workers,

    // Recycle a worker after this many requests. A Swoole worker is resident,
    // so anything an app leaks into a static accumulates across every request
    // it ever serves. This bounds the damage from a leak you have not found.
    'max_request' => (int)(getenv('TINA4_SWOOLE_MAX_REQUEST') ?: 10000),

    'enable_coroutine' => true,
]);

$http->on('request', function ($req, $res) use ($app) {
    try {
        $response = $app($req);

        $res->status($response->getStatusCode());

        foreach ($response->getHeaders() as $name => $value) {
            $res->header($name, $value);
        }

        // Cookies are a SEPARATE bag from headers on Tina4\Response. Drop this
        // loop and every Set-Cookie vanishes, which silently breaks sessions
        // and logins while every response still returns 200.
        foreach ($response->getCookies() as $name => $cookie) {
            $res->cookie(
                $name,
                $cookie['value'],
                $cookie['expires'],
                $cookie['path'],
                $cookie['domain'],
                $cookie['secure'],
                $cookie['httponly'],
                $cookie['samesite']
            );
        }

        $res->end($response->getBody());
    } catch (\Throwable $e) {
        // A throw that escapes here would kill the worker and take every
        // in-flight coroutine with it, so it is contained and logged instead.
        \Tina4\Log::error('Unhandled error in the Swoole request handler: ' . $e->getMessage(), [
            'file' => $e->getFile(),
            'line' => $e->getLine(),
        ]);
        $res->status(500);
        $res->end('Internal Server Error');
    }
});

$http->start();