diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2c707436..087875d5 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
- php-version: '8.1'
+ php-version: '8.2'
- name: Validate composer.json and composer.lock
run: composer validate --strict
diff --git a/Dockerfile.fpm b/Dockerfile.fpm
index 54c948a6..4b17ec96 100644
--- a/Dockerfile.fpm
+++ b/Dockerfile.fpm
@@ -13,15 +13,15 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
-FROM php:8.0-cli-alpine as final
+FROM php:8.2-fpm-alpine AS final
LABEL maintainer="team@appwrite.io"
ENV DEBIAN_FRONTEND=noninteractive \
- PHP_VERSION=8
+ PHP_FPM_POOL_CONF=/usr/local/etc/php-fpm.d/www.conf
RUN \
apk add --no-cache --virtual .deps \
- supervisor php$PHP_VERSION php$PHP_VERSION-fpm php$PHP_VERSION-mbstring nginx bash
+ supervisor nginx bash
# Nginx Configuration (with self-signed ssl certificates)
@@ -29,7 +29,7 @@ COPY ./tests/docker/nginx.conf /etc/nginx/nginx.conf
# PHP Configuration
RUN mkdir -p /var/run/php
-COPY ./tests/docker/www.conf /etc/php/$PHP_VERSION/fpm/pool.d/www.conf
+COPY ./tests/docker/www.conf /usr/local/etc/php-fpm.d/www.conf
# Script
COPY ./tests/docker/start /usr/local/bin/start
diff --git a/Dockerfile.swoole b/Dockerfile.swoole
index 6e0fdba1..ff80ec9f 100644
--- a/Dockerfile.swoole
+++ b/Dockerfile.swoole
@@ -13,7 +13,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
-FROM appwrite/base:0.4.3 as final
+FROM appwrite/base:0.5.0 AS final
LABEL maintainer="team@appwrite.io"
WORKDIR /usr/src/code
diff --git a/README.md b/README.md
index 137e384c..f55045f4 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
Utopia HTTP is a PHP MVC based framework with minimal must-have features for professional, simple, advanced and secure web development. This library is maintained by the [Appwrite team](https://appwrite.io).
-Utopia HTTP is dependency-free. Any extra features, such as authentication or caching are available as standalone models in order to keep the framework core clean, light, and easy to learn.
+Utopia HTTP keeps routing and request lifecycle concerns separate from resource wiring by relying on the standalone Utopia DI package for dependency injection.
## Getting Started
@@ -23,11 +23,14 @@ Init your first application in `src/server.php`:
```php
require_once __DIR__.'/../vendor/autoload.php';
+use Utopia\DI\Container;
use Utopia\Http\Http;
use Utopia\Http\Request;
use Utopia\Http\Response;
use Utopia\Http\Adapter\FPM\Server;
+$container = new Container();
+
Http::get('/hello-world') // Define Route
->inject('request')
->inject('response')
@@ -43,7 +46,7 @@ Http::get('/hello-world') // Define Route
Http::setMode(Http::MODE_TYPE_PRODUCTION);
-$http = new Http(new Server(), 'America/New_York');
+$http = new Http(new Server(), 'America/New_York', $container);
$http->start();
```
@@ -66,10 +69,13 @@ The library supports server adapters to be able to run on any PHP setup. You cou
#### Use PHP FPM server
```php
+use Utopia\DI\Container;
use Utopia\Http\Http;
use Utopia\Http\Response;
use Utopia\Http\Adapter\FPM\Server;
+$container = new Container();
+
Http::get('/')
->inject('response')
->action(
@@ -78,7 +84,7 @@ Http::get('/')
}
);
-$http = new Http(new Server(), 'America/New_York');
+$http = new Http(new Server(), 'America/New_York', $container);
$http->start();
```
@@ -87,11 +93,14 @@ $http->start();
#### Using Swoole server
```php
+use Utopia\DI\Container;
use Utopia\Http\Http;
use Utopia\Http\Request;
use Utopia\Http\Response;
use Utopia\Http\Adapter\Swoole\Server;
+$container = new Container();
+
Http::get('/')
->inject('request')
->inject('response')
@@ -101,7 +110,7 @@ Http::get('/')
}
);
-$http = new Http(new Server('0.0.0.0', '80'), 'America/New_York');
+$http = new Http(new Server('0.0.0.0', '80'), 'America/New_York', $container);
$http->start();
```
@@ -208,12 +217,12 @@ Groups are designed to be actions that run during the lifecycle of requests to e
### Resources
-Resources allow you to prepare dependencies for requests such as database connection or the user who sent the request. A new instance of a resource is created for every request.
+Resources allow you to prepare dependencies for requests such as database connections or shared services. Register application dependencies on the DI container with `set()`. Runtime values such as `request`, `response`, `route`, `error`, and `context` are scoped by `Http` for each request.
-Define a resource:
+Define a dependency on the DI container:
```php
-Http::setResource('timing', function() {
+$container->set('bootTime', function () {
return \microtime(true);
});
```
@@ -221,11 +230,13 @@ Http::setResource('timing', function() {
Inject resource into endpoint action:
```php
+$http = new Http(new Server(), 'America/New_York', $container);
+
Http::get('/')
- ->inject('timing')
+ ->inject('bootTime')
->inject('response')
- ->action(function(float $timing, Response $response) {
- $response->send('Request Unix timestamp: ' . \strval($timing));
+ ->action(function(float $bootTime, Response $response) {
+ $response->send('Process started at: ' . \strval($bootTime));
});
```
@@ -233,10 +244,10 @@ Inject resource into a hook:
```php
Http::shutdown()
- ->inject('timing')
- ->action(function(float $timing) {
- $difference = \microtime(true) - $timing;
- \var_dump("Request took: " . $difference . " seconds");
+ ->inject('bootTime')
+ ->action(function(float $bootTime) {
+ $uptime = \microtime(true) - $bootTime;
+ \var_dump("Process uptime: " . $uptime . " seconds");
});
```
@@ -248,7 +259,7 @@ To learn more about architecture and features for this library, check out more i
## System Requirements
-Utopia HTTP requires PHP 8.1 or later. We recommend using the latest PHP version whenever possible.
+Utopia HTTP requires PHP 8.2 or later. We recommend using the latest PHP version whenever possible.
## More from Utopia
diff --git a/composer.json b/composer.json
index 2c5bf2c6..40e0392d 100644
--- a/composer.json
+++ b/composer.json
@@ -29,15 +29,23 @@
"bench": "vendor/bin/phpbench run --report=benchmark"
},
"require": {
- "php": ">=8.0",
- "ext-swoole": "*",
- "utopia-php/validators": "0.2.*"
+ "php": ">=8.2",
+ "utopia-php/di": "0.3.*",
+ "utopia-php/validators": "0.2.*",
+ "ext-swoole": "*"
+ },
+ "config": {
+ "allow-plugins": {
+ "php-http/discovery": true,
+ "tbachert/spi": true
+ }
},
"require-dev": {
- "phpunit/phpunit": "^9.5.25",
+ "doctrine/instantiator": "^1.5",
"laravel/pint": "1.*",
- "swoole/ide-helper": "4.8.3",
+ "phpbench/phpbench": "^1.2",
"phpstan/phpstan": "1.*",
- "phpbench/phpbench": "^1.2"
+ "phpunit/phpunit": "^9.5.25",
+ "swoole/ide-helper": "4.8.3"
}
}
diff --git a/composer.lock b/composer.lock
index 3d5b401a..c854c2ab 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,112 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "e7104035855b324e167057fd44e2a875",
+ "content-hash": "5afd948989df91d546b1694354e2f9d2",
"packages": [
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "utopia-php/di",
+ "version": "0.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/di.git",
+ "reference": "68873b7267842315d01d82a83b988bae525eab31"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/di/zipball/68873b7267842315d01d82a83b988bae525eab31",
+ "reference": "68873b7267842315d01d82a83b988bae525eab31",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/container": "^2.0"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.27",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/phpstan": "^2.1",
+ "phpunit/phpunit": "^9.5.25",
+ "swoole/ide-helper": "4.8.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\": "src/",
+ "Tests\\E2E\\": "tests/e2e"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple and lite library for managing dependency injections",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "dependency-injection",
+ "di",
+ "php",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/di/issues",
+ "source": "https://github.com/utopia-php/di/tree/0.3.1"
+ },
+ "time": "2026-03-13T05:47:23+00:00"
+ },
{
"name": "utopia-php/validators",
"version": "0.2.0",
@@ -132,29 +236,30 @@
},
{
"name": "doctrine/instantiator",
- "version": "2.1.0",
+ "version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7"
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7",
- "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": ""
},
"require": {
- "php": "^8.4"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "doctrine/coding-standard": "^14",
+ "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*",
"ext-phar": "*",
- "phpbench/phpbench": "^1.2",
- "phpstan/phpstan": "^2.1",
- "phpstan/phpstan-phpunit": "^2.0",
- "phpunit/phpunit": "^10.5.58"
+ "phpbench/phpbench": "^0.16 || ^1",
+ "phpstan/phpstan": "^1.4",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.30 || ^5.4"
},
"type": "library",
"autoload": {
@@ -181,7 +286,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
- "source": "https://github.com/doctrine/instantiator/tree/2.1.0"
+ "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
},
"funding": [
{
@@ -197,7 +302,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-05T06:47:08+00:00"
+ "time": "2022-12-30T00:15:36+00:00"
},
{
"name": "doctrine/lexer",
@@ -278,16 +383,16 @@
},
{
"name": "laravel/pint",
- "version": "v1.27.1",
+ "version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5"
+ "reference": "bdec963f53172c5e36330f3a400604c69bf02d39"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/54cca2de13790570c7b6f0f94f37896bee4abcb5",
- "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39",
+ "reference": "bdec963f53172c5e36330f3a400604c69bf02d39",
"shasum": ""
},
"require": {
@@ -298,13 +403,14 @@
"php": "^8.2.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.93.1",
- "illuminate/view": "^12.51.0",
- "larastan/larastan": "^3.9.2",
+ "friendsofphp/php-cs-fixer": "^3.94.2",
+ "illuminate/view": "^12.54.1",
+ "larastan/larastan": "^3.9.3",
"laravel-zero/framework": "^12.0.5",
"mockery/mockery": "^1.6.12",
- "nunomaduro/termwind": "^2.3.3",
- "pestphp/pest": "^3.8.5"
+ "nunomaduro/termwind": "^2.4.0",
+ "pestphp/pest": "^3.8.6",
+ "shipfastlabs/agent-detector": "^1.1.0"
},
"bin": [
"builds/pint"
@@ -341,7 +447,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2026-02-10T20:00:20+00:00"
+ "time": "2026-03-12T15:51:39+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -632,16 +738,16 @@
},
{
"name": "phpbench/phpbench",
- "version": "1.4.3",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpbench/phpbench.git",
- "reference": "b641dde59d969ea42eed70a39f9b51950bc96878"
+ "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878",
- "reference": "b641dde59d969ea42eed70a39f9b51950bc96878",
+ "url": "https://api.github.com/repos/phpbench/phpbench/zipball/9a28fd0833f11171b949843c6fd663eb69b6d14c",
+ "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c",
"shasum": ""
},
"require": {
@@ -652,7 +758,7 @@
"ext-reflection": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
- "php": "^8.1",
+ "php": "^8.2",
"phpbench/container": "^2.2",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"seld/jsonlint": "^1.1",
@@ -672,8 +778,9 @@
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
- "phpunit/phpunit": "^10.4 || ^11.0",
+ "phpunit/phpunit": "^11.5",
"rector/rector": "^1.2",
+ "sebastian/exporter": "^6.3.2",
"symfony/error-handler": "^6.1 || ^7.0 || ^8.0",
"symfony/var-dumper": "^6.1 || ^7.0 || ^8.0"
},
@@ -718,7 +825,7 @@
],
"support": {
"issues": "https://github.com/phpbench/phpbench/issues",
- "source": "https://github.com/phpbench/phpbench/tree/1.4.3"
+ "source": "https://github.com/phpbench/phpbench/tree/1.5.1"
},
"funding": [
{
@@ -726,15 +833,15 @@
"type": "github"
}
],
- "time": "2025-11-06T19:07:31+00:00"
+ "time": "2026-03-05T08:18:58+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.12.32",
+ "version": "1.12.33",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8",
- "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1",
+ "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1",
"shasum": ""
},
"require": {
@@ -779,7 +886,7 @@
"type": "github"
}
],
- "time": "2025-09-30T10:16:31+00:00"
+ "time": "2026-02-28T20:30:03+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -1260,59 +1367,6 @@
},
"time": "2021-02-03T23:26:27+00:00"
},
- {
- "name": "psr/container",
- "version": "2.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/container.git",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "shasum": ""
- },
- "require": {
- "php": ">=7.4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Container\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
- }
- ],
- "description": "Common Container Interface (PHP FIG PSR-11)",
- "homepage": "https://github.com/php-fig/container",
- "keywords": [
- "PSR-11",
- "container",
- "container-interface",
- "container-interop",
- "psr"
- ],
- "support": {
- "issues": "https://github.com/php-fig/container/issues",
- "source": "https://github.com/php-fig/container/tree/2.0.2"
- },
- "time": "2021-11-05T16:47:00+00:00"
- },
{
"name": "psr/log",
"version": "3.0.2",
@@ -2482,16 +2536,16 @@
},
{
"name": "symfony/console",
- "version": "v8.0.4",
+ "version": "v8.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b"
+ "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b",
- "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b",
+ "url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
+ "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"shasum": ""
},
"require": {
@@ -2548,7 +2602,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v8.0.4"
+ "source": "https://github.com/symfony/console/tree/v8.0.7"
},
"funding": [
{
@@ -2568,7 +2622,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-13T13:06:50+00:00"
+ "time": "2026-03-06T14:06:22+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2639,16 +2693,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v8.0.1",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "d937d400b980523dc9ee946bb69972b5e619058d"
+ "reference": "7bf9162d7a0dff98d079b72948508fa48018a770"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d",
- "reference": "d937d400b980523dc9ee946bb69972b5e619058d",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770",
+ "reference": "7bf9162d7a0dff98d079b72948508fa48018a770",
"shasum": ""
},
"require": {
@@ -2685,7 +2739,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v8.0.1"
+ "source": "https://github.com/symfony/filesystem/tree/v8.0.6"
},
"funding": [
{
@@ -2705,20 +2759,20 @@
"type": "tidelift"
}
],
- "time": "2025-12-01T09:13:36+00:00"
+ "time": "2026-02-25T16:59:43+00:00"
},
{
"name": "symfony/finder",
- "version": "v8.0.5",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0"
+ "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0",
- "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c",
+ "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c",
"shasum": ""
},
"require": {
@@ -2753,7 +2807,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v8.0.5"
+ "source": "https://github.com/symfony/finder/tree/v8.0.6"
},
"funding": [
{
@@ -2773,7 +2827,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-26T15:08:38+00:00"
+ "time": "2026-01-29T09:41:02+00:00"
},
{
"name": "symfony/options-resolver",
@@ -3335,16 +3389,16 @@
},
{
"name": "symfony/string",
- "version": "v8.0.4",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "758b372d6882506821ed666032e43020c4f57194"
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
- "reference": "758b372d6882506821ed666032e43020c4f57194",
+ "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"shasum": ""
},
"require": {
@@ -3401,7 +3455,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v8.0.4"
+ "source": "https://github.com/symfony/string/tree/v8.0.6"
},
"funding": [
{
@@ -3421,7 +3475,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-12T12:37:40+00:00"
+ "time": "2026-02-09T10:14:57+00:00"
},
{
"name": "theseer/tokenizer",
@@ -3529,7 +3583,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=8.0",
+ "php": ">=8.2",
"ext-swoole": "*"
},
"platform-dev": {},
diff --git a/docker-compose.yml b/docker-compose.yml
index 0c4e0011..322de5ba 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: '3'
-
services:
fpm:
build:
diff --git a/docs/Getting-Starting-Guide.md b/docs/Getting-Starting-Guide.md
index 831d0687..86455959 100644
--- a/docs/Getting-Starting-Guide.md
+++ b/docs/Getting-Starting-Guide.md
@@ -10,33 +10,21 @@ If you’re new to Utopia, let’s get started by looking at an example of a bas
```php
use Utopia\Http\Http;
-use Utopia\Http\Swoole\Request;
-use Utopia\Http\Swoole\Response;
-use Swoole\Http\Server;
-use Swoole\Http\Request as SwooleRequest;
-use Swoole\Http\Response as SwooleResponse;
-
-$http = new Server("0.0.0.0", 8080);
+use Utopia\Http\Request;
+use Utopia\Http\Response;
+use Utopia\Http\Adapter\Swoole\Server;
Http::get('/')
->inject('request')
->inject('response')
->action(
- function($request, $response) {
+ function(Request $request, Response $response) {
// Return raw HTML
$response->send("
Hello World!
");
}
-/*
- Configure your HTTP server to respond with the Utopia http.
-*/
-
-$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
- $request = new Request($swooleRequest);
- $response = new Response($swooleResponse);
- $http = new Http('America/Toronto');
- $http->run($request, $response);
-});
+ );
+$http = new Http(new Server("0.0.0.0", "8080"), 'America/Toronto');
$http->start();
```
@@ -281,7 +269,6 @@ Http::shutdown(function($request) {
# Running Locally
If you have PHP and Composer installed on your device, you can run Utopia apps locally by downloading the `utopia-php/http` dependency using `composer require utopia-php/http` command.
-> Utopia HTTP requires PHP 8.1 or later. We recommend using the latest PHP version whenever possible.
+> Utopia HTTP requires PHP 8.2 or later. We recommend using the latest PHP version whenever possible.
Wonderful! 😄 You’re all set to create a basic demo app using the Utopia HTTP. If you have any issues or questions feel free to reach out to us on our [Discord Server](https://appwrite.io/discord).
-
diff --git a/example/Dockerfile b/example/Dockerfile
index 3abcbeed..2153e4e8 100644
--- a/example/Dockerfile
+++ b/example/Dockerfile
@@ -3,7 +3,7 @@ WORKDIR /usr/local/src/
COPY composer.* /usr/local/src/
RUN composer install --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist
-FROM appwrite/base:0.4.3 as final
+FROM appwrite/base:0.5.0 AS final
WORKDIR /usr/src/code
COPY ./src /usr/src/code/src
COPY --from=step0 /usr/local/src/vendor /usr/src/code/vendor
diff --git a/src/Http/Adapter.php b/src/Http/Adapter.php
index 082ca6ab..f72d6bfb 100755
--- a/src/Http/Adapter.php
+++ b/src/Http/Adapter.php
@@ -2,9 +2,12 @@
namespace Utopia\Http;
+use Utopia\DI\Container;
+
abstract class Adapter
{
abstract public function onStart(callable $callback);
abstract public function onRequest(callable $callback);
abstract public function start();
+ abstract public function getContainer(): Container;
}
diff --git a/src/Http/Adapter/FPM/Server.php b/src/Http/Adapter/FPM/Server.php
index e90f0cd7..d48d6fec 100755
--- a/src/Http/Adapter/FPM/Server.php
+++ b/src/Http/Adapter/FPM/Server.php
@@ -2,12 +2,12 @@
namespace Utopia\Http\Adapter\FPM;
+use Utopia\DI\Container;
use Utopia\Http\Adapter;
-use Utopia\Http\Http;
class Server extends Adapter
{
- public function __construct()
+ public function __construct(private Container $container)
{
}
@@ -16,15 +16,20 @@ public function onRequest(callable $callback)
$request = new Request();
$response = new Response();
- Http::setResource('fpmRequest', fn () => $request);
- Http::setResource('fpmResponse', fn () => $response);
+ $this->container->set('fpmRequest', fn () => $request);
+ $this->container->set('fpmResponse', fn () => $response);
- call_user_func($callback, $request, $response, 'fpm');
+ \call_user_func($callback, $request, $response);
}
public function onStart(callable $callback)
{
- call_user_func($callback, $this);
+ \call_user_func($callback, $this);
+ }
+
+ public function getContainer(): Container
+ {
+ return $this->container;
}
public function start()
diff --git a/src/Http/Adapter/Swoole/Server.php b/src/Http/Adapter/Swoole/Server.php
index bb3b87d8..57b4f5fb 100755
--- a/src/Http/Adapter/Swoole/Server.php
+++ b/src/Http/Adapter/Swoole/Server.php
@@ -4,41 +4,54 @@
use Swoole\Coroutine;
use Utopia\Http\Adapter;
+use Utopia\DI\Container;
use Swoole\Coroutine\Http\Server as SwooleServer;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
-use Utopia\Http\Http;
use function Swoole\Coroutine\run;
class Server extends Adapter
{
protected SwooleServer $server;
+ protected const REQUEST_CONTAINER_CONTEXT_KEY = '__utopia_http_request_container';
+ protected Container $container;
- public function __construct(string $host, ?string $port = null, array $settings = [])
+ public function __construct(string $host, ?string $port = null, array $settings = [], ?Container $container = null)
{
$this->server = new SwooleServer($host, $port);
$this->server->set(\array_merge($settings, [
'enable_coroutine' => true,
'http_parse_cookie' => false,
]));
+ $this->container = $container ?? new Container();
}
public function onRequest(callable $callback)
{
$this->server->handle('/', function (SwooleRequest $request, SwooleResponse $response) use ($callback) {
- $context = \strval(Coroutine::getCid());
+ $requestContainer = new Container($this->container);
+ $requestContainer->set('swooleRequest', fn () => $request);
+ $requestContainer->set('swooleResponse', fn () => $response);
- Http::setResource('swooleRequest', fn () => $request, [], $context);
- Http::setResource('swooleResponse', fn () => $response, [], $context);
+ Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] = $requestContainer;
- call_user_func($callback, new Request($request), new Response($response), $context);
+ $utopiaRequest = new Request($request);
+ $utopiaResponse = new Response($response);
+
+ \call_user_func($callback, $utopiaRequest, $utopiaResponse);
});
}
+ public function getContainer(): Container
+ {
+ return Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? $this->container;
+ }
+
public function onStart(callable $callback)
{
- call_user_func($callback, $this);
+
+ \call_user_func($callback, $this);
}
public function start()
diff --git a/src/Http/Http.php b/src/Http/Http.php
index 61434842..d3556a38 100755
--- a/src/Http/Http.php
+++ b/src/Http/Http.php
@@ -2,6 +2,7 @@
namespace Utopia\Http;
+use Utopia\DI\Container;
use Utopia\Validator;
class Http
@@ -32,22 +33,14 @@ class Http
public const MODE_TYPE_PRODUCTION = 'production';
- /**
- * @var array
- */
- protected array $resources = [
- 'error' => null,
- ];
-
/**
* @var Files
*/
protected Files $files;
- /**
- * @var array
- */
- protected static array $resourcesCallbacks = [];
+ protected Container $container;
+
+ protected ?Container $requestContainer = null;
/**
* Current running mode
@@ -351,78 +344,53 @@ public static function setAllowOverride(bool $value): void
}
/**
- * If a resource has been created return it, otherwise create it and then return it
- *
- * @param string $name
- * @param bool $fresh
- * @return mixed
+ * Get a single resource from the given scope.
*
* @throws Exception
*/
- public function getResource(string $name, string $context = 'utopia', bool $fresh = false): mixed
+ public function getResource(string $name): mixed
{
- if ($name === 'utopia') {
- return $this;
- }
-
- $this->resources[$context] ??= [];
-
- $resourcesCallback = &self::$resourcesCallbacks[$context] ?? [];
- if (empty($resourcesCallback) || !\array_key_exists($name, $resourcesCallback)) {
- $resourcesCallback = &self::$resourcesCallbacks['utopia'];
- }
+ try {
+ return $this->server->getContainer()->get($name);
+ } catch (\Throwable $e) {
+ // Normalize DI container errors to the Http layer's "resource" terminology.
+ $message = \str_replace('dependency', 'resource', $e->getMessage());
- if (!\array_key_exists($name, $this->resources[$context]) || $fresh || ($resourcesCallback[$name]['reset'][$context] ?? true)) {
- if (!\array_key_exists($name, $resourcesCallback)) {
- throw new Exception('Failed to find resource: "' . $name . '"');
+ if ($message === $e->getMessage() && !\str_contains($message, 'resource')) {
+ $message = 'Failed to find resource: "' . $name . '"';
}
- $this->resources[$context][$name] = \call_user_func_array(
- $resourcesCallback[$name]['callback'],
- $this->getResources($resourcesCallback[$name]['injections'], $context)
- );
+ throw new Exception($message, 500, $e);
}
-
- $resourcesCallback[$name]['reset'][$context] = false;
- return $this->resources[$context][$name];
}
/**
- * Get Resources By List
+ * Get multiple resources from the given scope.
*
- * @param array $list
- * @return array
+ * @param string[] $list
+ * @return array
+ *
+ * @throws Exception
*/
- public function getResources(array $list, string $context = 'utopia'): array
+ public function getResources(array $list): array
{
$resources = [];
foreach ($list as $name) {
- $resources[$name] = $this->getResource($name, $context);
+ $resources[$name] = $this->getResource($name);
}
return $resources;
}
/**
- * Set a new resource callback
- *
- * @param string $name
- * @param callable $callback
- * @param array $injections
- * @return void
+ * Set a resource on the given scope.
*
- * @throws Exception
+ * @param string[] $injections
*/
- public static function setResource(string $name, callable $callback, array $injections = [], string $context = 'utopia'): void
+ public function setResource(string $name, callable $callback, array $injections = []): void
{
- if ($name === 'utopia') {
- throw new Exception("'utopia' is a reserved keyword.", 500);
- }
-
- self::$resourcesCallbacks[$context] ??= [];
-
- self::$resourcesCallbacks[$context][$name] = ['callback' => $callback, 'injections' => $injections, 'resets' => []];
+ $this->server->getContainer()->set($name, $callback, $injections);
}
/**
@@ -574,34 +542,28 @@ public static function onRequest(): Hook
public function start()
{
- $this->server->onRequest(function ($request, $response, $context) {
- try {
- $this->run($request, $response, $context);
- } finally {
- if (isset(self::$resourcesCallbacks[$context])) {
- unset(self::$resourcesCallbacks[$context]);
- }
- }
- });
+
+ $this->server->onRequest(
+ fn (Request $request, Response $response) => $this->run($request, $response)
+ );
+
$this->server->onStart(function ($server) {
- $this->resources['utopia'] ??= [];
- $this->resources['utopia']['server'] = $server;
- self::setResource('server', function () use ($server) {
+ $this->setResource('server', function () use ($server) {
return $server;
});
try {
foreach (self::$startHooks as $hook) {
- $arguments = $this->getArguments($hook, 'utopia', [], []);
+ $arguments = $this->getArguments($hook, [], []);
\call_user_func_array($hook->getAction(), $arguments);
}
} catch (\Exception $e) {
- self::setResource('error', fn () => $e);
+ $this->setResource('error', fn () => $e);
foreach (self::$errors as $error) { // Global error hooks
if (in_array('*', $error->getGroups())) {
try {
- $arguments = $this->getArguments($error, 'utopia', [], []);
+ $arguments = $this->getArguments($error, [], []);
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
@@ -644,7 +606,7 @@ public function match(Request $request, bool $fresh = true): ?Route
* @param Route $route
* @param Request $request
*/
- public function execute(Route $route, Request $request, string $context): static
+ public function execute(Route $route, Request $request): static
{
$arguments = [];
$groups = $route->getGroups();
@@ -654,7 +616,7 @@ public function execute(Route $route, Request $request, string $context): static
if ($route->getHook()) {
foreach (self::$init as $hook) { // Global init hooks
if (in_array('*', $hook->getGroups())) {
- $arguments = $this->getArguments($hook, $context, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
@@ -662,20 +624,20 @@ public function execute(Route $route, Request $request, string $context): static
foreach ($groups as $group) {
foreach (self::$init as $hook) { // Group init hooks
- if (in_array($group, $hook->getGroups())) {
- $arguments = $this->getArguments($hook, $context, $pathValues, $request->getParams());
+ if (\in_array($group, $hook->getGroups())) {
+ $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}
- $arguments = $this->getArguments($route, $context, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($route, $pathValues, $request->getParams());
\call_user_func_array($route->getAction(), $arguments);
foreach ($groups as $group) {
foreach (self::$shutdown as $hook) { // Group shutdown hooks
- if (in_array($group, $hook->getGroups())) {
- $arguments = $this->getArguments($hook, $context, $pathValues, $request->getParams());
+ if (\in_array($group, $hook->getGroups())) {
+ $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
@@ -683,20 +645,20 @@ public function execute(Route $route, Request $request, string $context): static
if ($route->getHook()) {
foreach (self::$shutdown as $hook) { // Group shutdown hooks
- if (in_array('*', $hook->getGroups())) {
- $arguments = $this->getArguments($hook, $context, $pathValues, $request->getParams());
+ if (\in_array('*', $hook->getGroups())) {
+ $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}
} catch (\Throwable $e) {
- self::setResource('error', fn () => $e, [], $context);
+ $this->setResource('error', fn () => $e, []);
foreach ($groups as $group) {
foreach (self::$errors as $error) { // Group error hooks
- if (in_array($group, $error->getGroups())) {
+ if (\in_array($group, $error->getGroups())) {
try {
- $arguments = $this->getArguments($error, $context, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($error, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
@@ -706,9 +668,9 @@ public function execute(Route $route, Request $request, string $context): static
}
foreach (self::$errors as $error) { // Global error hooks
- if (in_array('*', $error->getGroups())) {
+ if (\in_array('*', $error->getGroups())) {
try {
- $arguments = $this->getArguments($error, $context, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($error, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
@@ -717,9 +679,6 @@ public function execute(Route $route, Request $request, string $context): static
}
}
- // Reset resources for the context
- $this->resources[$context] = [];
-
return $this;
}
@@ -733,7 +692,7 @@ public function execute(Route $route, Request $request, string $context): static
*
* @throws Exception
*/
- protected function getArguments(Hook $hook, string $context, array $values, array $requestParams): array
+ protected function getArguments(Hook $hook, array $values, array $requestParams): array
{
$arguments = [];
foreach ($hook->getParams() as $key => $param) { // Get value from route or request object
@@ -753,7 +712,7 @@ protected function getArguments(Hook $hook, string $context, array $values, arra
}
if ($paramExists) {
- $this->validate($key, $param, $value, $context);
+ $this->validate($key, $param, $value);
}
}
@@ -762,7 +721,7 @@ protected function getArguments(Hook $hook, string $context, array $values, arra
}
foreach ($hook->getInjections() as $key => $injection) {
- $arguments[$injection['order']] = $this->getResource($injection['name'], $context);
+ $arguments[$injection['order']] = $this->getResource($injection['name']);
}
return $arguments;
@@ -777,31 +736,23 @@ protected function getArguments(Hook $hook, string $context, array $values, arra
* @param Request $request
* @param Response $response;
*/
- public function run(Request $request, Response $response, string $context): static
+ public function run(Request $request, Response $response): static
{
- $this->resources[$context] = [];
- $this->resources[$context]['request'] = $request;
- $this->resources[$context]['response'] = $response;
-
- self::setResource('context', fn () => $context, [], $context);
-
- self::setResource('request', fn () => $request, [], $context);
-
- self::setResource('response', fn () => $response, [], $context);
+ $this->setResource('request', fn () => $request);
+ $this->setResource('response', fn () => $response);
try {
-
foreach (self::$requestHooks as $hook) {
- $arguments = $this->getArguments($hook, $context, [], []);
+ $arguments = $this->getArguments($hook, [], []);
\call_user_func_array($hook->getAction(), $arguments);
}
} catch (\Exception $e) {
- self::setResource('error', fn () => $e, [], $context);
+ $this->setResource('error', fn () => $e, []);
foreach (self::$errors as $error) { // Global error hooks
- if (in_array('*', $error->getGroups())) {
+ if (\in_array('*', $error->getGroups())) {
try {
- $arguments = $this->getArguments($error, $context, [], []);
+ $arguments = $this->getArguments($error, [], []);
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
@@ -821,11 +772,12 @@ public function run(Request $request, Response $response, string $context): stat
return $this;
}
+
$method = $request->getMethod();
$route = $this->match($request);
$groups = ($route instanceof Route) ? $route->getGroups() : [];
- self::setResource('route', fn () => $route, [], $context);
+ $this->setResource('route', fn () => $route, []);
if (self::REQUEST_METHOD_HEAD == $method) {
$method = self::REQUEST_METHOD_GET;
@@ -837,26 +789,26 @@ public function run(Request $request, Response $response, string $context): stat
foreach ($groups as $group) {
foreach (self::$options as $option) { // Group options hooks
/** @var Hook $option */
- if (in_array($group, $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
+ if (\in_array($group, $option->getGroups())) {
+ \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
}
}
}
foreach (self::$options as $option) { // Global options hooks
/** @var Hook $option */
- if (in_array('*', $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
+ if (\in_array('*', $option->getGroups())) {
+ \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
}
}
} catch (\Throwable $e) {
foreach (self::$errors as $error) { // Global error hooks
/** @var Hook $error */
- if (in_array('*', $error->getGroups())) {
- self::setResource('error', function () use ($e) {
+ if (\in_array('*', $error->getGroups())) {
+ $this->setResource('error', function () use ($e) {
return $e;
- }, [], $context);
- \call_user_func_array($error->getAction(), $this->getArguments($error, $context, [], $request->getParams()));
+ }, []);
+ \call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
}
}
}
@@ -870,43 +822,41 @@ public function run(Request $request, Response $response, string $context): stat
$path = \parse_url($request->getURI(), PHP_URL_PATH);
$route->path($path);
- self::setResource('route', fn () => $route, [], $context);
+ $this->setResource('route', fn () => $route, []);
}
if (null !== $route) {
- return $this->execute($route, $request, $context);
+ return $this->execute($route, $request);
} elseif (self::REQUEST_METHOD_OPTIONS == $method) {
try {
foreach ($groups as $group) {
foreach (self::$options as $option) { // Group options hooks
- if (in_array($group, $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
+ if (\in_array($group, $option->getGroups())) {
+ \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
}
}
}
foreach (self::$options as $option) { // Global options hooks
- if (in_array('*', $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
+ if (\in_array('*', $option->getGroups())) {
+ \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
}
}
} catch (\Throwable $e) {
foreach (self::$errors as $error) { // Global error hooks
- if (in_array('*', $error->getGroups())) {
- self::setResource('error', function () use ($e) {
+ if (\in_array('*', $error->getGroups())) {
+ $this->setResource('error', function () use ($e) {
return $e;
- }, [], $context);
- \call_user_func_array($error->getAction(), $this->getArguments($error, $context, [], $request->getParams()));
+ }, []);
+ \call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
}
}
}
} else {
foreach (self::$errors as $error) { // Global error hooks
- if (in_array('*', $error->getGroups())) {
- self::setResource('error', function () {
- return new Exception('Not Found', 404);
- }, [], $context);
- \call_user_func_array($error->getAction(), $this->getArguments($error, $context, [], $request->getParams()));
+ if (\in_array('*', $error->getGroups())) {
+ $this->setResource('error', fn () => new Exception('Not Found', 404), []);
+ \call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
}
}
}
@@ -914,6 +864,7 @@ public function run(Request $request, Response $response, string $context): stat
return $this;
}
+
/**
* Validate Param
*
@@ -926,7 +877,7 @@ public function run(Request $request, Response $response, string $context): stat
*
* @throws Exception
*/
- protected function validate(string $key, array $param, mixed $value, $context): void
+ protected function validate(string $key, array $param, mixed $value): void
{
if ($param['optional'] && \is_null($value)) {
return;
@@ -935,7 +886,7 @@ protected function validate(string $key, array $param, mixed $value, $context):
$validator = $param['validator']; // checking whether the class exists
if (\is_callable($validator)) {
- $validator = \call_user_func_array($validator, $this->getResources($param['injections'], $context));
+ $validator = \call_user_func_array($validator, $this->getResources($param['injections']));
}
if (!$validator instanceof Validator) { // is the validator object an instance of the Validator class
@@ -955,12 +906,13 @@ protected function validate(string $key, array $param, mixed $value, $context):
public static function reset(): void
{
Router::reset();
- self::$resourcesCallbacks = [];
self::$mode = '';
self::$errors = [];
self::$init = [];
self::$shutdown = [];
self::$options = [];
self::$startHooks = [];
+ self::$requestHooks = [];
+ self::$wildcardRoute = null;
}
}
diff --git a/src/Http/Response.php b/src/Http/Response.php
index 2bc4a5f4..444bc4f8 100755
--- a/src/Http/Response.php
+++ b/src/Http/Response.php
@@ -224,6 +224,8 @@ abstract class Response
*/
protected bool $sent = false;
+ protected bool $headersSent = false;
+
/**
* @var array>
*/
@@ -488,6 +490,8 @@ public function send(string $body = ''): void
->appendCookies()
->appendHeaders();
+ $this->headersSent = true;
+
if (!$this->disablePayload) {
$length = strlen($body);
@@ -566,9 +570,13 @@ public function chunk(string $body = '', bool $end = false): void
$this->addHeader('X-Debug-Speed', (string) (microtime(true) - $this->startTime));
- $this
- ->appendCookies()
- ->appendHeaders();
+ if (!$this->headersSent) {
+ $this
+ ->appendCookies()
+ ->appendHeaders();
+
+ $this->headersSent = true;
+ }
if (!$this->disablePayload) {
$this->write($body);
diff --git a/tests/HttpTest.php b/tests/HttpTest.php
index a735a41f..a6ce883e 100755
--- a/tests/HttpTest.php
+++ b/tests/HttpTest.php
@@ -3,6 +3,7 @@
namespace Utopia\Http;
use PHPUnit\Framework\TestCase;
+use Utopia\DI\Container;
use Utopia\Http\Tests\UtopiaFPMRequestTest;
use Utopia\Validator\Text;
use Utopia\Http\Adapter\FPM\Request;
@@ -13,6 +14,8 @@ class HttpTest extends TestCase
{
protected ?Http $http;
+ protected ?Container $container;
+
protected ?string $method;
protected ?string $uri;
@@ -20,13 +23,15 @@ class HttpTest extends TestCase
public function setUp(): void
{
Http::reset();
- $this->http = new Http(new Server(), 'Asia/Tel_Aviv');
+ $this->container = new Container();
+ $this->http = new Http(new Server($this->container), 'Asia/Tel_Aviv');
$this->saveRequest();
}
public function tearDown(): void
{
$this->http = null;
+ $this->container = null;
$this->restoreRequest();
}
@@ -80,76 +85,10 @@ public function testCanGetEnvironmentVariable(): void
$this->assertEquals(Http::getEnv('unknown', 'test'), 'test');
}
- public function testCanGetResources(): void
- {
- Http::setResource('rand', fn () => rand());
- Http::setResource('first', fn ($second) => "first-{$second}", ['second']);
- Http::setResource('second', fn () => 'second');
-
- $second = $this->http->getResource('second', '1');
- $first = $this->http->getResource('first', '1');
- $this->assertEquals('second', $second);
- $this->assertEquals('first-second', $first);
-
- $resource = $this->http->getResource('rand', '1');
-
- $this->assertNotEmpty($resource);
- $this->assertEquals($resource, $this->http->getResource('rand', '1'));
- $this->assertEquals($resource, $this->http->getResource('rand', '1'));
- $this->assertEquals($resource, $this->http->getResource('rand', '1'));
-
- // Default Params
- $route = new Route('GET', '/path');
-
- $route
- ->inject('rand')
- ->param('x', 'x-def', new Text(200), 'x param', true)
- ->param('y', 'y-def', new Text(200), 'y param', true)
- ->action(function ($x, $y, $rand) {
- echo $x . '-' . $y . '-' . $rand;
- });
-
- \ob_start();
- $this->http->execute($route, new Request(), '1');
- $result = \ob_get_contents();
- \ob_end_clean();
-
- $this->assertEquals('x-def-y-def-' . $resource, $result);
- }
-
- public function testCanGetDefaultValueWithFunction(): void
- {
- Http::setResource('first', fn ($second) => "first-{$second}", ['second']);
- Http::setResource('second', fn () => 'second');
-
- $second = $this->http->getResource('second');
- $first = $this->http->getResource('first');
- $this->assertEquals('second', $second);
- $this->assertEquals('first-second', $first);
-
- // Default Value using function
- $route = new Route('GET', '/path');
-
- $route
- ->param('x', function ($first, $second) {
- return $first . '-' . $second;
- }, new Text(200), 'x param', true, ['first', 'second'])
- ->action(function ($x) {
- echo $x;
- });
-
- \ob_start();
- $this->http->execute($route, new Request(), '1');
- $result = \ob_get_contents();
- \ob_end_clean();
-
- $this->assertEquals('first-second-second', $result);
- }
-
public function testCanExecuteRoute(): void
{
- Http::setResource('rand', fn () => rand());
- $resource = $this->http->getResource('rand', '1');
+ $this->container->set('rand', fn () => rand());
+ $resource = $this->container->get('rand');
$this->http
->error()
@@ -169,12 +108,12 @@ public function testCanExecuteRoute(): void
});
\ob_start();
- $this->http->execute($route, new Request(), '1');
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
// With Params
- $resource = $this->http->getResource('rand', '1');
+ $resource = $this->container->get('rand');
$route = new Route('GET', '/path');
$route
@@ -193,14 +132,14 @@ public function testCanExecuteRoute(): void
\ob_start();
$request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y', 'z' => 'param-z']);
- $this->http->execute($route, $request, '1');
+ $this->http->execute($route, $request);
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals($resource . '-param-x-param-y', $result);
// With Error
- $resource = $this->http->getResource('rand', '1');
+ $resource = $this->container->get('rand');
$route = new Route('GET', '/path');
$route
@@ -213,14 +152,14 @@ public function testCanExecuteRoute(): void
\ob_start();
$request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y']);
- $this->http->execute($route, $request, '1');
+ $this->http->execute($route, $request);
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals('error: Invalid `x` param: Value must be a valid string and no longer than 1 chars', $result);
// With Hooks
- $resource = $this->http->getResource('rand', '1');
+ $resource = $this->container->get('rand');
$this->http
->init()
->inject('rand')
@@ -285,17 +224,17 @@ public function testCanExecuteRoute(): void
\ob_start();
$request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y']);
- $this->http->execute($route, $request, '1');
+ $this->http->execute($route, $request);
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals('init-' . $resource . '-(init-api)-param-x-param-y-(shutdown-api)-shutdown', $result);
- $resource = $this->http->getResource('rand', '1');
+ $resource = $this->container->get('rand');
\ob_start();
$request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y']);
- $this->http->execute($homepage, $request, '1');
+ $this->http->execute($homepage, $request);
$result = \ob_get_contents();
\ob_end_clean();
@@ -325,7 +264,7 @@ public function testCanAddAndExecuteHooks()
});
\ob_start();
- $this->http->execute($route, new Request(), '1');
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -341,7 +280,7 @@ public function testCanAddAndExecuteHooks()
});
\ob_start();
- $this->http->execute($route, new Request(), '1');
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -409,7 +348,7 @@ public function testCanHookThrowExceptions()
});
\ob_start();
- $this->http->execute($route, new Request(), '1');
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -417,7 +356,7 @@ public function testCanHookThrowExceptions()
\ob_start();
$_GET['y'] = 'y-def';
- $this->http->execute($route, new Request(), '1');
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -561,7 +500,7 @@ public function testCanRunRequest(): void
});
\ob_start();
- $this->http->run(new Request(), new Response(), '1');
+ $this->http->run(new Request(), new Response());
$result = \ob_get_contents();
\ob_end_clean();
@@ -582,7 +521,7 @@ public function testWildcardRoute(): void
Http::init()
->action(function () {
$route = $this->http->getRoute();
- Http::setResource('myRoute', fn () => $route);
+ $this->container->set('myRoute', fn () => $route);
});
@@ -598,7 +537,7 @@ public function testWildcardRoute(): void
});
\ob_start();
- @$this->http->run(new Request(), new Response(), '1');
+ @$this->http->run(new Request(), new Response());
$result = \ob_get_contents();
\ob_end_clean();
@@ -607,7 +546,7 @@ public function testWildcardRoute(): void
\ob_start();
$req = new Request();
$req = $req->setMethod('OPTIONS');
- @$this->http->run($req, new Response(), '1');
+ @$this->http->run($req, new Response());
$result = \ob_get_contents();
\ob_end_clean();
@@ -631,7 +570,7 @@ public function testCallableStringParametersNotExecuted(): void
});
\ob_start();
- $this->http->execute($route, new Request(), '1');
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -649,7 +588,7 @@ public function testCallableStringParametersNotExecuted(): void
\ob_start();
$request = new UtopiaFPMRequestTest();
$request::_setParams(['func' => 'system']);
- $this->http->execute($route2, $request, '1');
+ $this->http->execute($route2, $request);
$result = \ob_get_contents();
\ob_end_clean();
@@ -667,7 +606,7 @@ public function testCallableStringParametersNotExecuted(): void
});
\ob_start();
- $this->http->execute($route3, new Request(), '1');
+ $this->http->execute($route3, new Request());
$result = \ob_get_contents();
\ob_end_clean();
diff --git a/tests/docker/start b/tests/docker/start
index f6df2a64..66526ef0 100755
--- a/tests/docker/start
+++ b/tests/docker/start
@@ -1,6 +1,6 @@
#!/bin/bash
-export PHP_VERSION=$PHP_VERSION
+POOL_CONF="${PHP_FPM_POOL_CONF:-/usr/local/etc/php-fpm.d/www.conf}"
chown -Rf www-data.www-data /usr/share/nginx/html/
@@ -12,13 +12,13 @@ function setEnvironmentVariable() {
fi
# Check whether variable already exists
- if ! grep -q "\[$1\]" /etc/php/$PHP_VERSION/fpm/pool.d/www.conf; then
+ if ! grep -q "\[$1\]" "$POOL_CONF"; then
# Add variable
- echo "env[$1] = $2" >> /etc/php/$PHP_VERSION/fpm/pool.d/www.conf
+ echo "env[$1] = $2" >> "$POOL_CONF"
fi
# Reset variable
- # sed -i "s/^env\[$1.*/env[$1] = $2/g" /etc/php/$PHP_VERSION/fpm/pool.d/www.conf
+ # sed -i "s/^env\[$1.*/env[$1] = $2/g" "$POOL_CONF"
}
# Start supervisord and services
diff --git a/tests/docker/supervisord.conf b/tests/docker/supervisord.conf
index 41f5c4d3..00c651f3 100644
--- a/tests/docker/supervisord.conf
+++ b/tests/docker/supervisord.conf
@@ -22,8 +22,8 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
-[program:php8-fpm]
-command=php-fpm%(ENV_PHP_VERSION)s -F
+[program:php-fpm]
+command=php-fpm -F
autostart=true
autorestart=true
priority=5
diff --git a/tests/e2e/server-fpm.php b/tests/e2e/server-fpm.php
index b886c118..eb15c7e2 100644
--- a/tests/e2e/server-fpm.php
+++ b/tests/e2e/server-fpm.php
@@ -2,9 +2,10 @@
require_once __DIR__.'/init.php';
+use Utopia\DI\Container;
use Utopia\Http\Adapter\FPM\Server;
use Utopia\Http\Http;
-$server = new Server();
+$server = new Server(new Container());
$http = new Http($server, 'UTC');
$http->start();