Merge pull request #11564 from appwrite/feat/migrate-di-container

feat: migrate from static Http::setResource() to DI Container
This commit is contained in:
Chirag Aggarwal
2026-04-07 13:58:10 +05:30
committed by GitHub
19 changed files with 2325 additions and 2214 deletions
+34 -38
View File
@@ -18,13 +18,15 @@ use Swoole\Timer;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Adapters\Generic;
use Utopia\CLI\CLI;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Dependency;
use Utopia\DI\Container;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Platform\Service;
@@ -47,7 +49,7 @@ require_once __DIR__ . '/controllers/general.php';
global $register;
$platform = new Appwrite();
$args = $platform->getEnv('argv');
$args = $_SERVER['argv'] ?? [];
\array_shift($args);
if (! isset($args[0])) {
@@ -56,21 +58,15 @@ if (! isset($args[0])) {
}
$taskName = $args[0];
$container = new Container();
$cli = new CLI(new Generic(), $_SERVER['argv'] ?? [], $container);
$platform->setCli($cli);
$platform->init(Service::TYPE_TASK);
$cli = $platform->getCli();
$setResource = function (string $name, callable $callback, array $injections = []) use ($cli) {
$dependency = new Dependency();
$dependency->setName($name)->setCallback($callback);
foreach ($injections as $injection) {
$dependency->inject($injection);
}
$cli->setResource($dependency);
};
$container->set('register', fn () => $register, []);
$setResource('register', fn () => $register, []);
$setResource('cache', function ($pools) {
$container->set('cache', function ($pools) {
$list = Config::getParam('pools-cache', []);
$adapters = [];
@@ -81,18 +77,18 @@ $setResource('cache', function ($pools) {
return new Cache(new Sharding($adapters));
}, ['pools']);
$setResource('pools', function (Registry $register) {
$container->set('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
$setResource('authorization', function () {
$container->set('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
$setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$container->set('dbForPlatform', function ($pools, $cache, $authorization) {
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
@@ -135,17 +131,17 @@ $setResource('dbForPlatform', function ($pools, $cache, $authorization) {
return $dbForPlatform;
}, ['pools', 'cache', 'authorization']);
$setResource('console', function () {
$container->set('console', function () {
return new Document(Config::getParam('console'));
}, []);
$setResource(
$container->set(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false,
[]
);
$setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
@@ -207,10 +203,10 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
$setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
return function (?Document $project = null) use ($pools, $cache, &$database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
return $database;
@@ -235,41 +231,41 @@ $setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $a
return $database;
};
}, ['pools', 'cache', 'authorization']);
$setResource('publisher', function (Group $pools) {
$container->set('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
$setResource('publisherDatabases', function (BrokerPool $publisher) {
$container->set('publisherDatabases', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('publisherFunctions', function (BrokerPool $publisher) {
$container->set('publisherFunctions', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('publisherMigrations', function (BrokerPool $publisher) {
$container->set('publisherMigrations', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('publisherMessaging', function (BrokerPool $publisher) {
$container->set('publisherMessaging', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('usage', function () {
$container->set('usage', function () {
return new UsageContext();
}, []);
$setResource('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
$container->set('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
$publisher,
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
), ['publisher']);
$setResource('queueForStatsResources', function (Publisher $publisher) {
$container->set('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
$setResource('queueForFunctions', function (Publisher $publisher) {
$container->set('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
$setResource('queueForDeletes', function (Publisher $publisher) {
$container->set('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
$setResource('queueForCertificates', function (Publisher $publisher) {
$container->set('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
$setResource('logError', function (Registry $register) {
$container->set('logError', function (Registry $register) {
return function (Throwable $error, string $namespace, string $action) use ($register) {
Console::error('[Error] Timestamp: ' . date('c', time()));
Console::error('[Error] Type: ' . get_class($error));
@@ -321,13 +317,13 @@ $setResource('logError', function (Registry $register) {
};
}, ['register']);
$setResource('executor', fn () => new Executor(), []);
$container->set('executor', fn () => new Executor(), []);
$setResource('bus', function (Registry $register) use ($cli) {
return $register->get('bus')->setResolver(fn (string $name) => $cli->getResource($name));
$container->set('bus', function (Registry $register) use ($container) {
return $register->get('bus')->setResolver(fn (string $name) => $container->get($name));
}, ['register']);
$setResource('telemetry', fn () => new NoTelemetry(), []);
$container->set('telemetry', fn () => new NoTelemetry(), []);
$exitCode = 0;
+4 -5
View File
@@ -752,11 +752,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
if (\is_array($values)) {
$count = 0;
foreach ($values as $value) {
$override = $count === 0;
$response->addHeader($name, $value, override: $override);
$count++;
$response->addHeader($name, $value);
}
} else {
$response->addHeader($name, $values);
@@ -1474,7 +1471,9 @@ Http::error()
try {
$cors = $utopia->getResource('cors');
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
$response->addHeader($name, $value, override: true);
$response
->removeHeader($name)
->addHeader($name, $value);
}
} catch (Throwable) {
// Degrade gracefully - error response without CORS is no worse than before.
+57 -54
View File
@@ -1,16 +1,13 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/init.php';
require_once __DIR__ . '/init/span.php';
global $register;
$registerRequestResources = require __DIR__ . '/init/resources/request.php';
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Swoole\Constant;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Swoole\Http\Server;
use Swoole\Process;
use Swoole\Table;
use Swoole\Timer;
@@ -29,6 +26,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Http\Adapter\Swoole\Server;
use Utopia\Http\Files;
use Utopia\Http\Http;
use Utopia\Logger\Log;
@@ -49,18 +47,35 @@ $certifiedDomains = new Table(100_000);
$certifiedDomains->column('value', Table::TYPE_INT, 1);
$certifiedDomains->create();
Http::setResource('riskyDomains', fn () => $riskyDomains);
Http::setResource('certifiedDomains', fn () => $certifiedDomains);
$http = new Server(
host: "0.0.0.0",
port: System::getEnv('PORT', 80),
mode: SWOOLE_PROCESS,
);
global $container;
$container->set('riskyDomains', fn () => $riskyDomains);
$container->set('certifiedDomains', fn () => $certifiedDomains);
$container->set('pools', function ($register) {
return $register->get('pools');
}, ['register']);
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$swooleAdapter = new Server(
host: "0.0.0.0",
port: System::getEnv('PORT', 80),
settings: [
Constant::OPTION_WORKER_NUM => $totalWorkers,
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
Constant::OPTION_HTTP_COMPRESSION => false,
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
],
container: $container,
);
$container->set('container', fn () => fn () => $swooleAdapter->getContainer());
$http = $swooleAdapter->getServer();
/**
* Assigns HTTP requests to worker threads by analyzing its payload/content.
*
@@ -69,16 +84,16 @@ $totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intva
* riskier tasks to a dedicated worker subset. Prefers idle workers, with fallback to random selection if necessary.
* doc: https://openswoole.com/docs/modules/swoole-server/configuration#dispatch_func
*
* @param Server $server Swoole server instance.
* @param \Swoole\Http\Server $server Swoole server instance.
* @param int $fd client ID
* @param int $type the type of data and its current state
* @param string|null $data Request content for categorization.
* @global int $totalThreads Total number of workers.
* @return int Chosen worker ID for the request.
*/
function dispatch(Server $server, int $fd, int $type, $data = null): int
function dispatch(\Swoole\Http\Server $server, int $fd, int $type, $data = null): int
{
$resolveWorkerId = function (Server $server, $data = null) {
$resolveWorkerId = function (\Swoole\Http\Server $server, $data = null) {
global $totalWorkers, $riskyDomains;
// If data is not set we can send request to any worker
@@ -161,18 +176,6 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
return $workerId;
}
$http
->set([
Constant::OPTION_WORKER_NUM => $totalWorkers,
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
Constant::OPTION_HTTP_COMPRESSION => false,
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
});
@@ -189,16 +192,14 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
Console::success('Reload completed...');
});
Http::setResource('bus', function ($register, $utopia) {
return $register->get('bus')->setResolver(fn (string $name) => $utopia->getResource($name));
}, ['register', 'utopia']);
$container->set('bus', function ($register) use ($swooleAdapter) {
return $register->get('bus')->setResolver(fn (string $name) => $swooleAdapter->getContainer()->get($name));
}, ['register']);
include __DIR__ . '/controllers/general.php';
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
{
$max = 15;
$sleep = 2;
$max = 15;
$sleep = 2;
$attempts = 0;
@@ -289,13 +290,13 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
Span::current()?->finish();
}
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $totalWorkers, $register) {
$app = new Http('UTC');
$http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorkers, $swooleAdapter) {
$app = new Http($swooleAdapter, 'UTC');
go(function () use ($register, $app) {
$pools = $register->get('pools');
/** @var \Utopia\Pools\Group $pools */
Http::setResource('pools', fn () => $pools);
/** @var \Utopia\Pools\Group $pools */
$pools = $app->getResource('pools');
go(function () use ($app, $pools) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
@@ -511,14 +512,11 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
});
});
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, $files) {
$swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swooleAdapter, $registerRequestResources) {
Span::init('http.request');
Http::setResource('swooleRequest', fn () => $swooleRequest);
Http::setResource('swooleResponse', fn () => $swooleResponse);
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
$request = new Request($utopiaRequest->getSwooleRequest());
$response = new Response($utopiaResponse->getSwooleResponse());
Span::add('http.method', $request->getMethod());
@@ -534,13 +532,18 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
return;
}
$app = new Http('UTC');
$requestContainer = $swooleAdapter->getContainer();
$requestContainer->set('request', fn () => $request);
$requestContainer->set('response', fn () => $response);
$app = new Http($swooleAdapter, 'UTC');
$requestContainer->set('utopia', fn () => $app);
$registerRequestResources($requestContainer);
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
$pools = $register->get('pools');
Http::setResource('pools', fn () => $pools);
try {
$authorization = $app->getResource('authorization');
@@ -624,6 +627,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
}
}
$swooleResponse = $utopiaResponse->getSwooleResponse();
$swooleResponse->setStatusCode(500);
$output = ((Http::isDevelopment())) ? [
@@ -647,11 +651,10 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
});
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
$http->on(Constant::EVENT_TASK, function () use ($register) {
$http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
Http::setResource('pools', fn () => $pools);
$app = new Http('UTC');
$app = new Http($swooleAdapter, 'UTC');
/** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform');
@@ -726,4 +729,4 @@ $http->on(Constant::EVENT_TASK, function () use ($register) {
});
});
$http->start();
$swooleAdapter->start();
+2 -13
View File
@@ -245,19 +245,8 @@ $register->set('pools', function () {
$maxConnections = (int) System::getEnv('_APP_CONNECTIONS_MAX', 151);
$instanceConnections = $maxConnections / (int) System::getEnv('_APP_POOL_CLIENTS', 14);
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
if ($multiprocessing) {
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
} else {
$workerCount = 1;
}
if ($workerCount > $instanceConnections) {
throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
}
$poolSize = (int)($instanceConnections / $workerCount);
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$poolSize = max(1, (int)($instanceConnections / $workerCount));
foreach ($connections as $key => $connection) {
$type = $connection['type'] ?? '';
+38 -1419
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+435
View File
@@ -0,0 +1,435 @@
<?php
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\Webhook;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Database\Documents\User;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit as UtopiaAudit;
use Utopia\Cache\Cache;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Container;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
/**
* Register per-job resources on the given container.
* These resources depend on the queue message or keep mutable state and
* must be fresh for each worker job.
*/
return function (Container $container): void {
$container->set('log', fn () => new Log(), []);
$container->set('usage', fn () => new Context(), []);
$container->set('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
$container->set('dbForPlatform', function (Cache $cache, Group $pools, Authorization $authorization) {
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class);
return $dbForPlatform;
}, ['cache', 'pools', 'authorization']);
$container->set('project', function ($message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
if ($project->isEmpty() || $project->getId() === 'console') {
return $project;
}
return $dbForPlatform->getDocument('projects', $project->getId());
}, ['message', 'dbForPlatform']);
$container->set('dbForProject', function (Cache $cache, Group $pools, Document $project, Database $dbForPlatform, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$database->setDocumentType('users', User::class);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
}, ['cache', 'pools', 'project', 'dbForPlatform', 'authorization']);
$container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, Cache $cache, Authorization $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$database->setAuthorization($authorization);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
return $database;
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
$container->set('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) {
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database {
$projectDocument ??= $project;
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
$databaseType = $database->getAttribute('type', '');
// Backwards-compatibility: older or seeded legacy databases may not have a DSN stored
// in the "database" attribute. In that case, fall back to the project's database DSN.
if ($databaseDSN === '') {
$databaseDSN = $projectDocument->getAttribute('database', '');
}
try {
$databaseDSN = new DSN($databaseDSN);
} catch (\InvalidArgumentException) {
$databaseDSN = new DSN('mysql://' . $databaseDSN);
}
try {
$dsn = new DSN($projectDocument->getAttribute('database'));
} catch (\InvalidArgumentException) {
// Temporary fallback until all projects use shared tables
$dsn = new DSN('mysql://' . $projectDocument->getAttribute('database'));
}
$pools = $register->get('pools');
$pool = $pools->get($databaseDSN->getHost());
$adapter = new DatabasePool($pool);
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization);
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables, true)) {
$database
->setSharedTables(true)
->setTenant((int) $projectDocument->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $projectDocument->getSequence());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['cache', 'register', 'project', 'authorization']);
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
return $database;
}
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
}
return $database;
};
}, ['pools', 'cache', 'authorization']);
$container->set('abuseRetention', function () {
return \time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
}, []);
$container->set('auditRetention', function (Document $project) {
if ($project->getId() === 'console') {
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
}
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
}, ['project']);
$container->set('executionRetention', function () {
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
}, []);
$container->set('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
$container->set('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
$container->set('queueForMails', function (Publisher $publisher) {
return new Mail($publisher);
}, ['publisher']);
$container->set('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
$container->set('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
$container->set('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
$container->set('queueForEvents', function (Publisher $publisher) {
return new Event($publisher);
}, ['publisher']);
$container->set('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
}, ['publisher']);
$container->set('queueForWebhooks', function (Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
$container->set('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
$container->set('queueForRealtime', function () {
return new Realtime();
}, []);
$container->set('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
$container->set('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
$container->set('deviceForSites', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForMigrations', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForFunctions', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForFiles', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForBuilds', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForCache', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('logError', function (Registry $register, Document $project) {
return function (Throwable $error, string $namespace, string $action, ?array $extras = null) use ($register, $project) {
$logger = $register->get('logger');
if ($logger) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace($namespace);
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', \get_class($error));
$log->addTag('projectId', $project->getId() ?? '');
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
}
$log->addExtra('previousFile', $error->getPrevious()->getFile());
$log->addExtra('previousLine', $error->getPrevious()->getLine());
}
foreach (($extras ?? []) as $key => $value) {
$log->addExtra($key, $value);
}
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
}
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
}
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
}
};
}, ['register', 'project']);
$container->set('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
if ($project->isEmpty() || $project->getId() === 'console') {
$adapter = new AdapterDatabase($dbForPlatform);
return new UtopiaAudit($adapter);
}
$dbForProject = $getProjectDB($project);
$adapter = new AdapterDatabase($dbForProject);
return new UtopiaAudit($adapter);
};
}, ['dbForPlatform', 'getProjectDB']);
$container->set('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
};
+20 -5
View File
@@ -33,7 +33,9 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Container;
use Utopia\DSN\DSN;
use Utopia\Http\Adapter\FPM\Server as HttpServer;
use Utopia\Http\Http;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
@@ -48,6 +50,8 @@ use Utopia\WebSocket\Server;
*/
require_once __DIR__ . '/init.php';
$registerRequestResources ??= require __DIR__ . '/init/resources/request.php';
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
// Log uncaught exceptions in one line instead of relying on Swoole's full backtrace dump
@@ -240,6 +244,11 @@ if (!function_exists('triggerStats')) {
}
}
global $container;
$container->set('pools', function ($register) {
return $register->get('pools');
}, ['register']);
$realtime = getRealtime();
/**
@@ -614,16 +623,22 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
Console::error('Failed to restart pub/sub...');
});
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime) {
$app = new Http('UTC');
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $registerRequestResources) {
global $container;
$request = new Request($request);
$response = new Response(new SwooleResponse());
Console::info("Connection open (user: {$connection})");
Http::setResource('pools', fn () => $register->get('pools'));
Http::setResource('request', fn () => $request);
Http::setResource('response', fn () => $response);
$connectionContainer = new Container($container);
$adapter = new HttpServer($connectionContainer);
$app = new Http($adapter, 'UTC');
$connectionContainer->set('utopia', fn () => $app);
$connectionContainer->set('request', fn () => $request);
$connectionContainer->set('response', fn () => $response);
$registerRequestResources($connectionContainer);
$project = null;
$logUser = null;
+42 -530
View File
@@ -1,564 +1,69 @@
<?php
require_once __DIR__ . '/init.php';
$registerWorkerMessageResources = require __DIR__ . '/init/worker/message.php';
use Appwrite\Certificates\LetsEncrypt;
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit as UtopiaAudit;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
use Utopia\Pools\Group;
use Utopia\Queue\Adapter\Swoole;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Message;
use Utopia\Queue\Publisher;
use Utopia\Queue\Queue;
use Utopia\Queue\Server;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
Runtime::enableCoroutine();
require_once __DIR__ . '/init/span.php';
global $register;
Server::setResource('register', fn () => $register);
global $container;
$container->set('pools', function ($register) {
return $register->get('pools');
}, ['register']);
Server::setResource('authorization', function () {
$container->set('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
$pools = $register->get('pools');
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$container->set('project', fn () => new Document([]), []);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class);
$container->set('log', fn () => new Log(), []);
return $dbForPlatform;
}, ['cache', 'register', 'authorization']);
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
if ($project->getId() === 'console') {
return $project;
}
return $dbForPlatform->getDocument('projects', $project->getId());
}, ['message', 'dbForPlatform']);
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
$pools = $register->get('pools');
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$database->setDocumentType('users', User::class);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$database->setAuthorization($authorization);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
return $database;
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
return $database;
}
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
}
return $database;
};
}, ['pools', 'cache', 'authorization']);
Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) {
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database {
$projectDocument ??= $project;
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
$databaseType = $database->getAttribute('type', '');
// Backwardscompatibility: older or seeded legacy databases may not have a DSN stored
// in the "database" attribute. In that case, fall back to the project's database DSN.
if ($databaseDSN === '') {
$databaseDSN = $projectDocument->getAttribute('database', '');
}
try {
$databaseDSN = new DSN($databaseDSN);
} catch (\InvalidArgumentException) {
$databaseDSN = new DSN('mysql://'.$databaseDSN);
}
try {
$dsn = new DSN($projectDocument->getAttribute('database'));
} catch (\InvalidArgumentException) {
// Temporary fallback until all projects use shared tables
$dsn = new DSN('mysql://' . $projectDocument->getAttribute('database'));
}
$pools = $register->get('pools');
$pool = $pools->get($databaseDSN->getHost());
$adapter = new DatabasePool($pool);
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization);
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables, true)) {
$database
->setSharedTables(true)
->setTenant((int) $projectDocument->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $projectDocument->getSequence());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['cache', 'register', 'project', 'authorization']);
Server::setResource('abuseRetention', function () {
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
});
Server::setResource('auditRetention', function (Document $project) {
if ($project->getId() === 'console') {
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
}
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
}, ['project']);
Server::setResource('executionRetention', function () {
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
});
Server::setResource('cache', function (Registry $register) {
$pools = $register->get('pools');
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = new CachePool($pools->get($value));
}
return new Cache(new Sharding($adapters));
}, ['register']);
Server::setResource('redis', function () {
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
$pass = System::getEnv('_APP_REDIS_PASS', '');
$redis = new \Redis();
@$redis->pconnect($host, (int) $port);
if ($pass) {
$redis->auth($pass);
}
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
});
Server::setResource('timelimit', function (\Redis $redis) {
return function (string $key, int $limit, int $time) use ($redis) {
return new TimeLimitRedis($key, $limit, $time, $redis);
};
}, ['redis']);
Server::setResource('log', fn () => new Log());
Server::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
Server::setResource('publisherDatabases', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
Server::setResource('publisherFunctions', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
Server::setResource('publisherMigrations', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
Server::setResource('publisherMessaging', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
Server::setResource('consumer', function (Group $pools) {
$container->set('consumer', function (Group $pools) {
return new BrokerPool(consumer: $pools->get('consumer'));
}, ['pools']);
Server::setResource('consumerDatabases', function (BrokerPool $consumer) {
$container->set('consumerDatabases', function (BrokerPool $consumer) {
return $consumer;
}, ['consumer']);
Server::setResource('consumerMigrations', function (BrokerPool $consumer) {
$container->set('consumerMigrations', function (BrokerPool $consumer) {
return $consumer;
}, ['consumer']);
Server::setResource('consumerStatsUsage', function (BrokerPool $consumer) {
$container->set('consumerStatsUsage', function (BrokerPool $consumer) {
return $consumer;
}, ['consumer']);
Server::setResource('usage', function () {
return new Context();
}, []);
Server::setResource('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
$publisher,
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
), ['publisher']);
Server::setResource('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
Server::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
Server::setResource('queueForMails', function (Publisher $publisher) {
return new Mail($publisher);
}, ['publisher']);
Server::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
Server::setResource('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
Server::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
Server::setResource('queueForEvents', function (Publisher $publisher) {
return new Event($publisher);
}, ['publisher']);
Server::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
}, ['publisher']);
Server::setResource('queueForWebhooks', function (Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
Server::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
Server::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
Server::setResource('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
Server::setResource('logger', function (Registry $register) {
return $register->get('logger');
}, ['register']);
Server::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
Server::setResource('telemetry', fn () => new NoTelemetry());
Server::setResource('deviceForSites', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForMigrations', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForFunctions', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForFiles', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForBuilds', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForCache', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
Server::setResource('plan', function (array $plan = []) {
return [];
});
Server::setResource('certificates', function () {
$container->set('certificates', function () {
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
if (empty($email)) {
throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue a LetsEncrypt SSL certificate.');
}
return new LetsEncrypt($email);
});
}, []);
Server::setResource('logError', function (Registry $register, Document $project) {
return function (Throwable $error, string $namespace, string $action, ?array $extras = null) use ($register, $project) {
$logger = $register->get('logger');
if ($logger) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace($namespace);
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', get_class($error));
$log->addTag('projectId', $project->getId() ?? '');
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
}
$log->addExtra('previousFile', $error->getPrevious()->getFile());
$log->addExtra('previousLine', $error->getPrevious()->getLine());
}
foreach (($extras ?? []) as $key => $value) {
$log->addExtra($key, $value);
}
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
}
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
}
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
}
};
}, ['register', 'project']);
Server::setResource('executor', fn () => new Executor());
Server::setResource('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
if ($project->isEmpty() || $project->getId() === 'console') {
$adapter = new AdapterDatabase($dbForPlatform);
return new UtopiaAudit($adapter);
}
$dbForProject = $getProjectDB($project);
$adapter = new AdapterDatabase($dbForProject);
return new UtopiaAudit($adapter);
};
}, ['dbForPlatform', 'getProjectDB']);
Server::setResource('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
$pools = $register->get('pools');
$platform = new Appwrite();
$args = $platform->getEnv('argv');
$args = $_SERVER['argv'] ?? [];
if (! isset($args[1])) {
Console::error('Missing worker name');
@@ -574,38 +79,45 @@ if (\str_starts_with($workerName, 'databases')) {
$queueName = System::getEnv('_APP_QUEUE_NAME', 'v1-' . strtolower($workerName));
}
/** @var \Utopia\Pools\Group $pools */
$pools = $container->get('pools');
$adapter = new Swoole(
$pools->get('consumer')->pop()->getResource(),
System::getEnv('_APP_WORKERS_NUM', 1),
$queueName
);
$worker = new Server($adapter, $container);
try {
/**
* Any worker can be configured with the following env vars:
* - _APP_WORKERS_NUM The total number of worker processes
* - _APP_WORKER_PER_CORE The number of worker processes per core (ignored if _APP_WORKERS_NUM is set)
* - _APP_QUEUE_NAME The name of the queue to read for database events
*/
$worker->init()->action(function () use ($worker, $registerWorkerMessageResources) {
$registerWorkerMessageResources($worker->getContainer());
});
$container->set('bus', function ($register) use ($worker) {
return $register->get('bus')->setResolver(
fn (string $name) => $worker->getContainer()->get($name)
);
}, ['register']);
$platform->setWorker($worker);
$platform->init(Service::TYPE_WORKER, [
'workersNum' => System::getEnv('_APP_WORKERS_NUM', 1),
'connection' => $pools->get('consumer')->pop()->getResource(),
'workerName' => strtolower($workerName) ?? null,
'queueName' => $queueName,
'workerName' => strtolower($workerName),
]);
} catch (\Throwable $e) {
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
Console::exit(1);
}
$worker = $platform->getWorker();
Server::setResource('bus', function ($register) use ($worker) {
return $register->get('bus')->setResolver(fn (string $name) => $worker->getResource($name));
}, ['register']);
$worker
->error()
->inject('error')
->inject('logger')
->inject('log')
->inject('pools')
->inject('project')
->inject('authorization')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($queueName) {
->action(function (Throwable $error, ?Logger $logger, Log $log, Document $project, Authorization $authorization) use ($queueName) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
+7 -8
View File
@@ -52,34 +52,34 @@
"appwrite/php-runtimes": "0.19.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.2.*",
"utopia-php/agents": "1.2.*",
"utopia-php/analytics": "0.15.*",
"utopia-php/audit": "2.2.*",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "1.0.*",
"utopia-php/cli": "0.22.*",
"utopia-php/cli": "0.23.*",
"utopia-php/compression": "0.1.*",
"utopia-php/config": "1.*",
"utopia-php/console": "0.1.*",
"utopia-php/database": "5.*",
"utopia-php/agents": "1.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "1.*",
"utopia-php/emails": "0.6.*",
"utopia-php/dns": "1.6.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/framework": "0.34.*",
"utopia-php/fetch": "0.5.*",
"utopia-php/image": "0.8.*",
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.22.*",
"utopia-php/migration": "1.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/platform": "0.12.*",
"utopia-php/pools": "1.*",
"utopia-php/span": "1.1.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.15.*",
"utopia-php/servers": "0.2.5",
"utopia-php/queue": "0.17.*",
"utopia-php/servers": "0.3.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "1.0.*",
"utopia-php/system": "0.10.*",
@@ -94,8 +94,7 @@
"spomky-labs/otphp": "11.*",
"webonyx/graphql-php": "14.11.*",
"league/csv": "9.14.*",
"enshrined/svg-sanitize": "0.22.*",
"utopia-php/di": "0.1.0"
"enshrined/svg-sanitize": "0.22.*"
},
"require-dev": {
"ext-fileinfo": "*",
Generated
+130 -67
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4fe91e67f343fbe6deac1fdc7eda949f",
"content-hash": "e9c38bbebc60849e70e3640aaa4422cd",
"packages": [
{
"name": "adhocore/jwt",
@@ -3403,16 +3403,16 @@
},
{
"name": "utopia-php/agents",
"version": "1.3.0",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/agents.git",
"reference": "06064fd9fb19b77ae45a12ec7bcbc17670912c30"
"reference": "052227953678a30ecc4b5467401fcb0b2386471e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/agents/zipball/06064fd9fb19b77ae45a12ec7bcbc17670912c30",
"reference": "06064fd9fb19b77ae45a12ec7bcbc17670912c30",
"url": "https://api.github.com/repos/utopia-php/agents/zipball/052227953678a30ecc4b5467401fcb0b2386471e",
"reference": "052227953678a30ecc4b5467401fcb0b2386471e",
"shasum": ""
},
"require": {
@@ -3450,9 +3450,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/agents/issues",
"source": "https://github.com/utopia-php/agents/tree/1.3.0"
"source": "https://github.com/utopia-php/agents/tree/1.2.1"
},
"time": "2026-03-26T03:51:11+00:00"
"time": "2026-02-24T06:03:55+00:00"
},
{
"name": "utopia-php/analytics",
@@ -3658,21 +3658,21 @@
},
{
"name": "utopia-php/cli",
"version": "0.22.0",
"version": "0.23.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cli.git",
"reference": "a7ac387ee626fd27075a87e836fb72c5be38add4"
"reference": "8d1955b8bc4dc631f45d7c7df689ed7b63f70621"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/a7ac387ee626fd27075a87e836fb72c5be38add4",
"reference": "a7ac387ee626fd27075a87e836fb72c5be38add4",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/8d1955b8bc4dc631f45d7c7df689ed7b63f70621",
"reference": "8d1955b8bc4dc631f45d7c7df689ed7b63f70621",
"shasum": ""
},
"require": {
"php": ">=7.4",
"utopia-php/servers": "0.2.*"
"utopia-php/servers": "0.3.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -3703,9 +3703,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cli/issues",
"source": "https://github.com/utopia-php/cli/tree/0.22.0"
"source": "https://github.com/utopia-php/cli/tree/0.23.1"
},
"time": "2025-10-21T10:42:45+00:00"
"time": "2026-04-05T15:27:35+00:00"
},
{
"name": "utopia-php/compression",
@@ -3954,25 +3954,26 @@
},
{
"name": "utopia-php/di",
"version": "0.1.0",
"version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/di.git",
"reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31"
"reference": "07025d721ed5d9be27932e8e640acf1467fc4b9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31",
"reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31",
"url": "https://api.github.com/repos/utopia-php/di/zipball/07025d721ed5d9be27932e8e640acf1467fc4b9d",
"reference": "07025d721ed5d9be27932e8e640acf1467fc4b9d",
"shasum": ""
},
"require": {
"php": ">=8.2"
"php": ">=8.2",
"psr/container": "^2.0"
},
"require-dev": {
"laravel/pint": "^1.2",
"laravel/pint": "^1.27",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^9.5.25",
"swoole/ide-helper": "4.8.3"
},
@@ -3989,16 +3990,18 @@
],
"description": "A simple and lite library for managing dependency injections",
"keywords": [
"framework",
"http",
"PSR-11",
"container",
"dependency-injection",
"di",
"php",
"upf"
"utopia"
],
"support": {
"issues": "https://github.com/utopia-php/di/issues",
"source": "https://github.com/utopia-php/di/tree/0.1.0"
"source": "https://github.com/utopia-php/di/tree/0.3.2"
},
"time": "2024-08-08T14:35:19+00:00"
"time": "2026-03-21T07:42:10+00:00"
},
{
"name": "utopia-php/dns",
@@ -4268,30 +4271,34 @@
},
{
"name": "utopia-php/framework",
"version": "0.33.41",
"version": "0.34.17",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "0f3bf2377c867e547c929c3733b8224afee6ef06"
"reference": "d3e4143b8b06d9823d0c29a06dacefa5a1b93677"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/0f3bf2377c867e547c929c3733b8224afee6ef06",
"reference": "0f3bf2377c867e547c929c3733b8224afee6ef06",
"url": "https://api.github.com/repos/utopia-php/http/zipball/d3e4143b8b06d9823d0c29a06dacefa5a1b93677",
"reference": "d3e4143b8b06d9823d0c29a06dacefa5a1b93677",
"shasum": ""
},
"require": {
"php": ">=8.3",
"ext-swoole": "*",
"php": ">=8.2",
"utopia-php/compression": "0.1.*",
"utopia-php/di": "0.3.*",
"utopia-php/servers": "0.3.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/validators": "0.2.*"
},
"require-dev": {
"doctrine/instantiator": "^1.5",
"laravel/pint": "1.*",
"phpbench/phpbench": "1.*",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "1.*",
"phpunit/phpunit": "9.*",
"swoole/ide-helper": "^6.0"
"phpunit/phpunit": "^9.5.25",
"swoole/ide-helper": "4.8.3"
},
"type": "library",
"autoload": {
@@ -4303,17 +4310,72 @@
"license": [
"MIT"
],
"description": "A simple, light and advanced PHP framework",
"description": "A simple, light and advanced PHP HTTP framework",
"keywords": [
"framework",
"http",
"php",
"upf"
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.33.41"
"source": "https://github.com/utopia-php/http/tree/0.34.17"
},
"time": "2026-02-24T12:01:28+00:00"
"time": "2026-04-06T04:40:23+00:00"
},
{
"name": "utopia-php/http",
"version": "0.34.16",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "2b4021ba3f9d476264ce9fd6703d6c79de9add7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/2b4021ba3f9d476264ce9fd6703d6c79de9add7f",
"reference": "2b4021ba3f9d476264ce9fd6703d6c79de9add7f",
"shasum": ""
},
"require": {
"ext-swoole": "*",
"php": ">=8.2",
"utopia-php/compression": "0.1.*",
"utopia-php/di": "0.3.*",
"utopia-php/servers": "0.3.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/validators": "0.2.*"
},
"require-dev": {
"doctrine/instantiator": "^1.5",
"laravel/pint": "1.*",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "1.*",
"phpunit/phpunit": "^9.5.25",
"swoole/ide-helper": "4.8.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A simple, light and advanced PHP HTTP framework",
"keywords": [
"framework",
"http",
"php",
"upf"
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.34.16"
},
"time": "2026-03-20T10:39:07+00:00"
},
{
"name": "utopia-php/image",
@@ -4634,30 +4696,30 @@
},
{
"name": "utopia-php/platform",
"version": "0.7.16",
"version": "0.12.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
"reference": "34e67e4b80b5741c380071fe765fbc12a132de4f"
"reference": "068ee46228f0c3972e6b569f2c86b6c80fe583d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/34e67e4b80b5741c380071fe765fbc12a132de4f",
"reference": "34e67e4b80b5741c380071fe765fbc12a132de4f",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/068ee46228f0c3972e6b569f2c86b6c80fe583d8",
"reference": "068ee46228f0c3972e6b569f2c86b6c80fe583d8",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-redis": "*",
"php": ">=8.0",
"utopia-php/cli": "0.22.*",
"utopia-php/framework": "0.33.*",
"utopia-php/queue": "0.15.*"
"php": ">=8.1",
"utopia-php/cli": "0.23.*",
"utopia-php/http": "0.34.*",
"utopia-php/queue": "0.17.*",
"utopia-php/servers": "0.3.*"
},
"require-dev": {
"laravel/pint": "1.*",
"phpstan/phpstan": "2.*",
"phpunit/phpunit": "9.*"
"laravel/pint": "1.2.*",
"phpunit/phpunit": "^9.3"
},
"type": "library",
"autoload": {
@@ -4679,9 +4741,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/0.7.16"
"source": "https://github.com/utopia-php/platform/tree/0.12.0"
},
"time": "2026-02-11T06:36:48+00:00"
"time": "2026-03-31T14:44:23+00:00"
},
{
"name": "utopia-php/pools",
@@ -4791,32 +4853,33 @@
},
{
"name": "utopia-php/queue",
"version": "0.15.6",
"version": "0.17.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "08e361d69610f371382b344c369eef355ca414b4"
"reference": "0fbc7d7312f5cf76ec112513fb93317000901f5f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/08e361d69610f371382b344c369eef355ca414b4",
"reference": "08e361d69610f371382b344c369eef355ca414b4",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/0fbc7d7312f5cf76ec112513fb93317000901f5f",
"reference": "0fbc7d7312f5cf76ec112513fb93317000901f5f",
"shasum": ""
},
"require": {
"php": ">=8.3",
"php-amqplib/php-amqplib": "^3.7",
"utopia-php/di": "0.3.*",
"utopia-php/fetch": "0.5.*",
"utopia-php/pools": "1.*",
"utopia-php/servers": "0.2.*",
"utopia-php/servers": "0.3.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/validators": "0.2.*"
},
"require-dev": {
"ext-redis": "*",
"laravel/pint": "^0.2.3",
"laravel/pint": "^1.0",
"phpstan/phpstan": "^1.8",
"phpunit/phpunit": "^9.5.5",
"phpunit/phpunit": "^11.0",
"swoole/ide-helper": "4.8.8",
"workerman/workerman": "^4.0"
},
@@ -4851,9 +4914,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.15.6"
"source": "https://github.com/utopia-php/queue/tree/0.17.0"
},
"time": "2026-02-23T13:03:51+00:00"
"time": "2026-03-23T16:21:31+00:00"
},
{
"name": "utopia-php/registry",
@@ -4909,21 +4972,21 @@
},
{
"name": "utopia-php/servers",
"version": "0.2.5",
"version": "0.3.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/servers.git",
"reference": "4770e879a90685af4ba14e7e5d95d0a17c7fdf03"
"reference": "235be31200df9437fc96a1c270ffef4c64fafe52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/servers/zipball/4770e879a90685af4ba14e7e5d95d0a17c7fdf03",
"reference": "4770e879a90685af4ba14e7e5d95d0a17c7fdf03",
"url": "https://api.github.com/repos/utopia-php/servers/zipball/235be31200df9437fc96a1c270ffef4c64fafe52",
"reference": "235be31200df9437fc96a1c270ffef4c64fafe52",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/di": "0.1.*",
"php": ">=8.2",
"utopia-php/di": "0.3.*",
"utopia-php/validators": "0.*"
},
"require-dev": {
@@ -4957,9 +5020,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/servers/issues",
"source": "https://github.com/utopia-php/servers/tree/0.2.5"
"source": "https://github.com/utopia-php/servers/tree/0.3.0"
},
"time": "2026-02-10T04:21:53+00:00"
"time": "2026-03-13T11:31:42+00:00"
},
{
"name": "utopia-php/span",
+19 -19
View File
@@ -26,9 +26,9 @@ class Resolvers
): callable {
return static fn ($type, $args, $context, $info) => new Swoole(
function (callable $resolve, callable $reject) use ($utopia, $route, $args) {
$utopia = $utopia->getResource('utopia:graphql', true);
$request = $utopia->getResource('request', true);
$response = $utopia->getResource('response', true);
$utopia = $utopia->getResource('utopia:graphql');
$request = $utopia->getResource('request');
$response = $utopia->getResource('response');
$path = $route->getPath();
foreach ($args as $key => $value) {
@@ -93,9 +93,9 @@ class Resolvers
): callable {
return static fn ($type, $args, $context, $info) => new Swoole(
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
$utopia = $utopia->getResource('utopia:graphql', true);
$request = $utopia->getResource('request', true);
$response = $utopia->getResource('response', true);
$utopia = $utopia->getResource('utopia:graphql');
$request = $utopia->getResource('request');
$response = $utopia->getResource('response');
$request->setMethod('GET');
$request->setURI($url($databaseId, $collectionId, $args));
@@ -124,9 +124,9 @@ class Resolvers
): callable {
return static fn ($type, $args, $context, $info) => new Swoole(
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
$utopia = $utopia->getResource('utopia:graphql', true);
$request = $utopia->getResource('request', true);
$response = $utopia->getResource('response', true);
$utopia = $utopia->getResource('utopia:graphql');
$request = $utopia->getResource('request');
$response = $utopia->getResource('response');
$request->setMethod('GET');
$request->setURI($url($databaseId, $collectionId, $args));
@@ -160,9 +160,9 @@ class Resolvers
): callable {
return static fn ($type, $args, $context, $info) => new Swoole(
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
$utopia = $utopia->getResource('utopia:graphql', true);
$request = $utopia->getResource('request', true);
$response = $utopia->getResource('response', true);
$utopia = $utopia->getResource('utopia:graphql');
$request = $utopia->getResource('request');
$response = $utopia->getResource('response');
$request->setMethod('POST');
$request->setURI($url($databaseId, $collectionId, $args));
@@ -192,9 +192,9 @@ class Resolvers
): callable {
return static fn ($type, $args, $context, $info) => new Swoole(
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
$utopia = $utopia->getResource('utopia:graphql', true);
$request = $utopia->getResource('request', true);
$response = $utopia->getResource('response', true);
$utopia = $utopia->getResource('utopia:graphql');
$request = $utopia->getResource('request');
$response = $utopia->getResource('response');
$request->setMethod('PATCH');
$request->setURI($url($databaseId, $collectionId, $args));
@@ -222,9 +222,9 @@ class Resolvers
): callable {
return static fn ($type, $args, $context, $info) => new Swoole(
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
$utopia = $utopia->getResource('utopia:graphql', true);
$request = $utopia->getResource('request', true);
$response = $utopia->getResource('response', true);
$utopia = $utopia->getResource('utopia:graphql');
$request = $utopia->getResource('request');
$response = $utopia->getResource('response');
$request->setMethod('DELETE');
$request->setURI($url($databaseId, $collectionId, $args));
@@ -266,7 +266,7 @@ class Resolvers
try {
$route = $utopia->match($request, fresh: true);
$utopia->execute($route, $request, $response);
$utopia->execute($route, $request);
} catch (\Throwable $e) {
if ($beforeReject) {
$e = $beforeReject($e);
-4
View File
@@ -32,10 +32,6 @@ class Schema
array $urls,
array $params,
): GQLSchema {
Http::setResource('utopia:graphql', static function () use ($utopia) {
return $utopia;
});
if (!empty(self::$schema)) {
return self::$schema;
}
+16 -16
View File
@@ -145,9 +145,20 @@ class Server
$paths = $this->paths;
$state = $this->state;
Http::setResource('installerState', fn () => $state);
Http::setResource('installerConfig', fn () => $config);
Http::setResource('installerPaths', fn () => $paths);
$adapter = new class ($host, $port, ['worker_num' => 1]) extends SwooleAdapter {
public function getNativeServer(): SwooleServer
{
return $this->server;
}
};
$nativeServer = $adapter->getNativeServer();
$container = $adapter->getContainer();
$container->set('installerState', fn () => $state);
$container->set('installerConfig', fn () => $config);
$container->set('installerPaths', fn () => $paths);
$container->set('swooleServer', fn () => $nativeServer);
// Register routes via Utopia Platform
$platform = new Installer();
@@ -160,17 +171,6 @@ class Server
->inject('response')
->action($errorHandler->action(...));
$adapter = new class ($host, $port, ['worker_num' => 1]) extends SwooleAdapter {
public function getNativeServer(): SwooleServer
{
return $this->server;
}
};
$nativeServer = $adapter->getNativeServer();
Http::setResource('swooleServer', fn () => $nativeServer);
$nativeServer->on('start', function () use ($nativeServer, $port, $readyFile) {
\Swoole\Process::signal(SIGTERM, fn () => $nativeServer->shutdown());
\Swoole\Process::signal(SIGINT, fn () => $nativeServer->shutdown());
@@ -180,7 +180,7 @@ class Server
}
});
$adapter->onRequest(function (Request $request, Response $response) use ($files) {
$adapter->onRequest(function (Request $request, Response $response) use ($adapter, $files) {
// Serve static files from memory
$uri = $request->getURI();
if ($files->isFileLoaded($uri)) {
@@ -190,7 +190,7 @@ class Server
return;
}
$app = new Http('UTC');
$app = new Http($adapter, 'UTC');
$app->run($request, $response);
});
@@ -182,19 +182,33 @@ class Update extends Action
$dbForDatabases = $getDatabasesDB($databaseDoc);
try {
$dbForDatabases->withTransaction(function () use ($dbForDatabases, $dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $authorization) {
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
'status' => 'committing',
])));
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
'transactions',
$transactionId,
new Document(['status' => 'committing'])
));
$operations = $authorization->skip(fn () => $dbForProject->find('transactionLogs', [
Query::equal('transactionInternalId', [$transaction->getSequence()]),
Query::orderAsc(),
Query::limit(PHP_INT_MAX),
]));
$operations = $authorization->skip(fn () => $dbForProject->find('transactionLogs', [
Query::equal('transactionInternalId', [$transaction->getSequence()]),
Query::orderAsc(),
Query::limit(PHP_INT_MAX),
]));
$collections = [];
foreach ($operations as $operation) {
$databaseInternalId = $operation['databaseInternalId'];
$collectionInternalId = $operation['collectionInternalId'];
$collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}";
if (!isset($collections[$collectionId])) {
$collections[$collectionId] = $authorization->skip(
fn () => $dbForProject->getCollection($collectionId)
);
}
}
$dbForDatabases->withTransaction(function () use ($dbForDatabases, $transactionState, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $collections) {
$state = [];
$collections = [];
foreach ($operations as $operation) {
$databaseInternalId = $operation['databaseInternalId'];
@@ -210,11 +224,6 @@ class Update extends Action
$data = $data->getArrayCopy();
}
if (!isset($collections[$collectionId])) {
$collections[$collectionId] = $authorization->skip(
fn () => $dbForProject->getCollection($collectionId)
);
}
$collection = $collections[$collectionId];
if (\is_array($data) && !empty($data)) {
@@ -276,16 +285,17 @@ class Update extends Action
}
}
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
'transactions',
$transactionId,
new Document(['status' => 'committed'])
));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($transaction);
});
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
'transactions',
$transactionId,
new Document(['status' => 'committed'])
));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($transaction);
} catch (NotFoundException $e) {
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
'status' => 'failed',
+3 -4
View File
@@ -9,7 +9,6 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception;
use Utopia\Database\Validator\Authorization;
use Utopia\Http\Http;
use Utopia\Platform\Action;
use Utopia\Registry\Registry;
use Utopia\Validator\Text;
@@ -32,6 +31,7 @@ class Migrate extends Action
->inject('getProjectDB')
->inject('register')
->inject('authorization')
->inject('console')
->callback($this->action(...));
}
@@ -48,7 +48,8 @@ class Migrate extends Action
Database $dbForPlatform,
callable $getProjectDB,
Registry $register,
Authorization $authorization
Authorization $authorization,
Document $console
): void {
if (!\array_key_exists($version, Migration::$versions)) {
@@ -125,8 +126,6 @@ class Migrate extends Action
Console::log('Migrated ' . ++$count . '/' . $total . ' projects...');
});
$console = (new Http('UTC'))->getResource('console');
try {
$migration
->setProject($console, $getProjectDB($console), $dbForPlatform, $authorization, $getProjectDB);
+16 -6
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\Network\Validator\Redirect;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Specification\Format\OpenAPI3;
@@ -18,6 +19,9 @@ use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\DI\Container;
use Utopia\Http\Adapter\FPM\Server as FPMServer;
use Utopia\Http\Http;
use Utopia\Http\Request as UtopiaRequest;
use Utopia\Http\Response as UtopiaResponse;
@@ -336,11 +340,17 @@ class Specs extends Action
$mocks = ($mode === 'mocks');
// Mock dependencies
Http::setResource('request', fn () => $this->getRequest());
Http::setResource('response', fn () => $response);
Http::setResource('dbForPlatform', fn () => new Database(new MySQL(''), new Cache(new None())));
Http::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
// Mock dependencies needed by param validator injections in route definitions
$specsContainer = new Container();
$specsContainer->set('request', fn () => $this->getRequest());
$specsContainer->set('response', fn () => $response);
$specsContainer->set('dbForPlatform', fn () => new Database(new MySQL(''), new Cache(new None())));
$specsContainer->set('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
$specsContainer->set('redirectValidator', fn () => new Redirect([], []));
$specsContainer->set('project', fn () => new Document([]));
$specsContainer->set('passwordsDictionary', fn () => []);
$specsContainer->set('localeCodes', fn () => \array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', [])));
$specsContainer->set('plan', fn () => []);
$platforms = static::getPlatforms();
$authCounts = $this->getAuthCounts();
@@ -438,7 +448,7 @@ class Specs extends Action
}
$arguments = [
new Http('UTC'),
new Http(new FPMServer($specsContainer), 'UTC'),
$services,
$routes,
$models,
+12 -1
View File
@@ -2,10 +2,14 @@
namespace Appwrite\Promises;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
use Utopia\DI\Container;
class Swoole extends Promise
{
private const REQUEST_CONTAINER_CONTEXT_KEY = '__utopia_http_request_container';
public function __construct(?callable $executor = null)
{
parent::__construct($executor);
@@ -16,7 +20,14 @@ class Swoole extends Promise
callable $resolve,
callable $reject
): void {
\go(function () use ($executor, $resolve, $reject) {
$parentContainer = (Coroutine::getCid() !== -1)
? (Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? null)
: null;
\go(function () use ($executor, $resolve, $reject, $parentContainer) {
if ($parentContainer !== null) {
Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] = new Container($parentContainer);
}
try {
$executor($resolve, $reject);
} catch (\Throwable $exception) {
+1 -1
View File
@@ -9,7 +9,7 @@ class MockPublisher implements Publisher
{
private array $events = [];
public function enqueue(Queue $queue, array $payload): bool
public function enqueue(Queue $queue, array $payload, bool $priority = false): bool
{
if (!isset($this->events[$queue->name])) {
$this->events[$queue->name] = [];