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..8d8eb428 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..d74aa5d3 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 377787e6..8b8177b6 100644
--- a/composer.json
+++ b/composer.json
@@ -29,14 +29,17 @@
"bench": "vendor/bin/phpbench run --report=benchmark"
},
"require": {
- "php": ">=8.0",
+ "php": ">=8.2",
+ "utopia-php/di": "0.3.*",
+ "utopia-php/validators": "0.2.*",
"ext-swoole": "*"
},
"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 abee3f7b..c854c2ab 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,158 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "47587a7a55803f5b0fdf225267c03f03",
- "packages": [],
+ "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",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/validators.git",
+ "reference": "30b6030a5b100fc1dff34506e5053759594b2a20"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/validators/zipball/30b6030a5b100fc1dff34506e5053759594b2a20",
+ "reference": "30b6030a5b100fc1dff34506e5053759594b2a20",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "laravel/pint": "1.*",
+ "phpstan/phpstan": "2.*",
+ "phpunit/phpunit": "11.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A lightweight collection of reusable validators for Utopia projects",
+ "keywords": [
+ "php",
+ "utopia",
+ "validation",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/validators/issues",
+ "source": "https://github.com/utopia-php/validators/tree/0.2.0"
+ },
+ "time": "2026-01-13T09:16:51+00:00"
+ }
+ ],
"packages-dev": [
{
"name": "doctrine/annotations",
@@ -81,34 +231,35 @@
"issues": "https://github.com/doctrine/annotations/issues",
"source": "https://github.com/doctrine/annotations/tree/2.0.2"
},
+ "abandoned": true,
"time": "2024-09-05T10:17:24+00:00"
},
{
"name": "doctrine/instantiator",
- "version": "2.0.0",
+ "version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": ""
},
"require": {
- "php": "^8.1"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "doctrine/coding-standard": "^11",
+ "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*",
"ext-phar": "*",
- "phpbench/phpbench": "^1.2",
- "phpstan/phpstan": "^1.9.4",
- "phpstan/phpstan-phpunit": "^1.3",
- "phpunit/phpunit": "^9.5.27",
- "vimeo/psalm": "^5.4"
+ "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": {
@@ -135,7 +286,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
- "source": "https://github.com/doctrine/instantiator/tree/2.0.0"
+ "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
},
"funding": [
{
@@ -151,7 +302,7 @@
"type": "tidelift"
}
],
- "time": "2022-12-30T00:23:10+00:00"
+ "time": "2022-12-30T00:15:36+00:00"
},
{
"name": "doctrine/lexer",
@@ -232,16 +383,16 @@
},
{
"name": "laravel/pint",
- "version": "v1.24.0",
+ "version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a"
+ "reference": "bdec963f53172c5e36330f3a400604c69bf02d39"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a",
- "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39",
+ "reference": "bdec963f53172c5e36330f3a400604c69bf02d39",
"shasum": ""
},
"require": {
@@ -252,22 +403,20 @@
"php": "^8.2.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.82.2",
- "illuminate/view": "^11.45.1",
- "larastan/larastan": "^3.5.0",
- "laravel-zero/framework": "^11.45.0",
+ "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.1",
- "pestphp/pest": "^2.36.0"
+ "nunomaduro/termwind": "^2.4.0",
+ "pestphp/pest": "^3.8.6",
+ "shipfastlabs/agent-detector": "^1.1.0"
},
"bin": [
"builds/pint"
],
"type": "project",
"autoload": {
- "files": [
- "overrides/Runner/Parallel/ProcessFactory.php"
- ],
"psr-4": {
"App\\": "app/",
"Database\\Seeders\\": "database/seeders/",
@@ -287,6 +436,7 @@
"description": "An opinionated code formatter for PHP.",
"homepage": "https://laravel.com",
"keywords": [
+ "dev",
"format",
"formatter",
"lint",
@@ -297,7 +447,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2025-07-10T18:09:32+00:00"
+ "time": "2026-03-12T15:51:39+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -361,16 +511,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.6.1",
+ "version": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2"
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
- "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"shasum": ""
},
"require": {
@@ -413,9 +563,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
},
- "time": "2025-08-13T20:13:15+00:00"
+ "time": "2025-12-06T11:56:16+00:00"
},
{
"name": "phar-io/manifest",
@@ -537,24 +687,24 @@
},
{
"name": "phpbench/container",
- "version": "2.2.2",
+ "version": "2.2.3",
"source": {
"type": "git",
"url": "https://github.com/phpbench/container.git",
- "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33"
+ "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpbench/container/zipball/a59b929e00b87b532ca6d0edd8eca0967655af33",
- "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33",
+ "url": "https://api.github.com/repos/phpbench/container/zipball/0c7b2d36c1ea53fe27302fb8873ded7172047196",
+ "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196",
"shasum": ""
},
"require": {
"psr/container": "^1.0|^2.0",
- "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0"
+ "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^2.16",
+ "php-cs-fixer/shim": "^3.89",
"phpstan/phpstan": "^0.12.52",
"phpunit/phpunit": "^8"
},
@@ -582,22 +732,22 @@
"description": "Simple, configurable, service container.",
"support": {
"issues": "https://github.com/phpbench/container/issues",
- "source": "https://github.com/phpbench/container/tree/2.2.2"
+ "source": "https://github.com/phpbench/container/tree/2.2.3"
},
- "time": "2023-10-30T13:38:26+00:00"
+ "time": "2025-11-06T09:05:13+00:00"
},
{
"name": "phpbench/phpbench",
- "version": "1.4.1",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpbench/phpbench.git",
- "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b"
+ "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b",
- "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b",
+ "url": "https://api.github.com/repos/phpbench/phpbench/zipball/9a28fd0833f11171b949843c6fd663eb69b6d14c",
+ "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c",
"shasum": ""
},
"require": {
@@ -608,30 +758,31 @@
"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",
- "symfony/console": "^6.1 || ^7.0",
- "symfony/filesystem": "^6.1 || ^7.0",
- "symfony/finder": "^6.1 || ^7.0",
- "symfony/options-resolver": "^6.1 || ^7.0",
- "symfony/process": "^6.1 || ^7.0",
+ "symfony/console": "^6.1 || ^7.0 || ^8.0",
+ "symfony/filesystem": "^6.1 || ^7.0 || ^8.0",
+ "symfony/finder": "^6.1 || ^7.0 || ^8.0",
+ "symfony/options-resolver": "^6.1 || ^7.0 || ^8.0",
+ "symfony/process": "^6.1 || ^7.0 || ^8.0",
"webmozart/glob": "^4.6"
},
"require-dev": {
"dantleech/invoke": "^2.0",
"ergebnis/composer-normalize": "^2.39",
- "friendsofphp/php-cs-fixer": "^3.0",
"jangregor/phpstan-prophecy": "^1.0",
- "phpspec/prophecy": "dev-master",
+ "php-cs-fixer/shim": "^3.9",
+ "phpspec/prophecy": "^1.22",
"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",
- "symfony/error-handler": "^6.1 || ^7.0",
- "symfony/var-dumper": "^6.1 || ^7.0"
+ "sebastian/exporter": "^6.3.2",
+ "symfony/error-handler": "^6.1 || ^7.0 || ^8.0",
+ "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0"
},
"suggest": {
"ext-xdebug": "For Xdebug profiling extension."
@@ -674,7 +825,7 @@
],
"support": {
"issues": "https://github.com/phpbench/phpbench/issues",
- "source": "https://github.com/phpbench/phpbench/tree/1.4.1"
+ "source": "https://github.com/phpbench/phpbench/tree/1.5.1"
},
"funding": [
{
@@ -682,20 +833,15 @@
"type": "github"
}
],
- "time": "2025-03-12T08:01:40+00:00"
+ "time": "2026-03-05T08:18:58+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.12.28",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9"
- },
+ "version": "1.12.33",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9",
- "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1",
+ "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1",
"shasum": ""
},
"require": {
@@ -740,7 +886,7 @@
"type": "github"
}
],
- "time": "2025-07-17T17:15:39+00:00"
+ "time": "2026-02-28T20:30:03+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -1063,16 +1209,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.25",
+ "version": "9.6.34",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "049c011e01be805202d8eebedef49f769a8ec7b7"
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/049c011e01be805202d8eebedef49f769a8ec7b7",
- "reference": "049c011e01be805202d8eebedef49f769a8ec7b7",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a",
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a",
"shasum": ""
},
"require": {
@@ -1094,10 +1240,10 @@
"phpunit/php-timer": "^5.0.3",
"sebastian/cli-parser": "^1.0.2",
"sebastian/code-unit": "^1.0.8",
- "sebastian/comparator": "^4.0.9",
+ "sebastian/comparator": "^4.0.10",
"sebastian/diff": "^4.0.6",
"sebastian/environment": "^5.1.5",
- "sebastian/exporter": "^4.0.6",
+ "sebastian/exporter": "^4.0.8",
"sebastian/global-state": "^5.0.8",
"sebastian/object-enumerator": "^4.0.4",
"sebastian/resource-operations": "^3.0.4",
@@ -1146,7 +1292,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.25"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34"
},
"funding": [
{
@@ -1170,7 +1316,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-20T14:38:31+00:00"
+ "time": "2026-01-27T05:45:00+00:00"
},
{
"name": "psr/cache",
@@ -1221,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",
@@ -1493,16 +1586,16 @@
},
{
"name": "sebastian/comparator",
- "version": "4.0.9",
+ "version": "4.0.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5"
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
- "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d",
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d",
"shasum": ""
},
"require": {
@@ -1555,7 +1648,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
- "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10"
},
"funding": [
{
@@ -1575,7 +1668,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-10T06:51:50+00:00"
+ "time": "2026-01-24T09:22:56+00:00"
},
{
"name": "sebastian/complexity",
@@ -1765,16 +1858,16 @@
},
{
"name": "sebastian/exporter",
- "version": "4.0.6",
+ "version": "4.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
- "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
"shasum": ""
},
"require": {
@@ -1830,15 +1923,27 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
- "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
}
],
- "time": "2024-03-02T06:33:00+00:00"
+ "time": "2025-09-24T06:03:27+00:00"
},
{
"name": "sebastian/global-state",
@@ -2431,47 +2536,39 @@
},
{
"name": "symfony/console",
- "version": "v7.3.2",
+ "version": "v8.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1"
+ "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1",
- "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1",
+ "url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
+ "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"shasum": ""
},
"require": {
- "php": ">=8.2",
- "symfony/deprecation-contracts": "^2.5|^3",
- "symfony/polyfill-mbstring": "~1.0",
+ "php": ">=8.4",
+ "symfony/polyfill-mbstring": "^1.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^7.2"
- },
- "conflict": {
- "symfony/dependency-injection": "<6.4",
- "symfony/dotenv": "<6.4",
- "symfony/event-dispatcher": "<6.4",
- "symfony/lock": "<6.4",
- "symfony/process": "<6.4"
+ "symfony/string": "^7.4|^8.0"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
- "symfony/config": "^6.4|^7.0",
- "symfony/dependency-injection": "^6.4|^7.0",
- "symfony/event-dispatcher": "^6.4|^7.0",
- "symfony/http-foundation": "^6.4|^7.0",
- "symfony/http-kernel": "^6.4|^7.0",
- "symfony/lock": "^6.4|^7.0",
- "symfony/messenger": "^6.4|^7.0",
- "symfony/process": "^6.4|^7.0",
- "symfony/stopwatch": "^6.4|^7.0",
- "symfony/var-dumper": "^6.4|^7.0"
+ "symfony/config": "^7.4|^8.0",
+ "symfony/dependency-injection": "^7.4|^8.0",
+ "symfony/event-dispatcher": "^7.4|^8.0",
+ "symfony/http-foundation": "^7.4|^8.0",
+ "symfony/http-kernel": "^7.4|^8.0",
+ "symfony/lock": "^7.4|^8.0",
+ "symfony/messenger": "^7.4|^8.0",
+ "symfony/process": "^7.4|^8.0",
+ "symfony/stopwatch": "^7.4|^8.0",
+ "symfony/var-dumper": "^7.4|^8.0"
},
"type": "library",
"autoload": {
@@ -2505,7 +2602,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.3.2"
+ "source": "https://github.com/symfony/console/tree/v8.0.7"
},
"funding": [
{
@@ -2525,7 +2622,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-30T17:13:41+00:00"
+ "time": "2026-03-06T14:06:22+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2596,25 +2693,25 @@
},
{
"name": "symfony/filesystem",
- "version": "v7.3.2",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd"
+ "reference": "7bf9162d7a0dff98d079b72948508fa48018a770"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd",
- "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770",
+ "reference": "7bf9162d7a0dff98d079b72948508fa48018a770",
"shasum": ""
},
"require": {
- "php": ">=8.2",
+ "php": ">=8.4",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
- "symfony/process": "^6.4|^7.0"
+ "symfony/process": "^7.4|^8.0"
},
"type": "library",
"autoload": {
@@ -2642,7 +2739,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v7.3.2"
+ "source": "https://github.com/symfony/filesystem/tree/v8.0.6"
},
"funding": [
{
@@ -2662,27 +2759,27 @@
"type": "tidelift"
}
],
- "time": "2025-07-07T08:17:47+00:00"
+ "time": "2026-02-25T16:59:43+00:00"
},
{
"name": "symfony/finder",
- "version": "v7.3.2",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe"
+ "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe",
- "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c",
+ "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c",
"shasum": ""
},
"require": {
- "php": ">=8.2"
+ "php": ">=8.4"
},
"require-dev": {
- "symfony/filesystem": "^6.4|^7.0"
+ "symfony/filesystem": "^7.4|^8.0"
},
"type": "library",
"autoload": {
@@ -2710,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/v7.3.2"
+ "source": "https://github.com/symfony/finder/tree/v8.0.6"
},
"funding": [
{
@@ -2730,24 +2827,24 @@
"type": "tidelift"
}
],
- "time": "2025-07-15T13:41:35+00:00"
+ "time": "2026-01-29T09:41:02+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v7.3.2",
+ "version": "v8.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
- "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37"
+ "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37",
- "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
+ "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
"shasum": ""
},
"require": {
- "php": ">=8.2",
+ "php": ">=8.4",
"symfony/deprecation-contracts": "^2.5|^3"
},
"type": "library",
@@ -2781,7 +2878,7 @@
"options"
],
"support": {
- "source": "https://github.com/symfony/options-resolver/tree/v7.3.2"
+ "source": "https://github.com/symfony/options-resolver/tree/v8.0.0"
},
"funding": [
{
@@ -2801,7 +2898,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-15T11:36:08+00:00"
+ "time": "2025-11-12T15:55:31+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -3140,20 +3237,20 @@
},
{
"name": "symfony/process",
- "version": "v7.3.0",
+ "version": "v8.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af"
+ "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
- "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
+ "url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
+ "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"shasum": ""
},
"require": {
- "php": ">=8.2"
+ "php": ">=8.4"
},
"type": "library",
"autoload": {
@@ -3181,7 +3278,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.3.0"
+ "source": "https://github.com/symfony/process/tree/v8.0.5"
},
"funding": [
{
@@ -3192,25 +3289,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-17T09:11:12+00:00"
+ "time": "2026-01-26T15:08:38+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.6.0",
+ "version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
- "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
"shasum": ""
},
"require": {
@@ -3264,7 +3365,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
},
"funding": [
{
@@ -3275,44 +3376,47 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-25T09:37:31+00:00"
+ "time": "2025-07-15T11:30:57+00:00"
},
{
"name": "symfony/string",
- "version": "v7.3.2",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca"
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca",
- "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca",
+ "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"shasum": ""
},
"require": {
- "php": ">=8.2",
- "symfony/polyfill-ctype": "~1.8",
- "symfony/polyfill-intl-grapheme": "~1.0",
- "symfony/polyfill-intl-normalizer": "~1.0",
- "symfony/polyfill-mbstring": "~1.0"
+ "php": ">=8.4",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-intl-grapheme": "^1.33",
+ "symfony/polyfill-intl-normalizer": "^1.0",
+ "symfony/polyfill-mbstring": "^1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
- "symfony/emoji": "^7.1",
- "symfony/error-handler": "^6.4|^7.0",
- "symfony/http-client": "^6.4|^7.0",
- "symfony/intl": "^6.4|^7.0",
+ "symfony/emoji": "^7.4|^8.0",
+ "symfony/http-client": "^7.4|^8.0",
+ "symfony/intl": "^7.4|^8.0",
"symfony/translation-contracts": "^2.5|^3.0",
- "symfony/var-exporter": "^6.4|^7.0"
+ "symfony/var-exporter": "^7.4|^8.0"
},
"type": "library",
"autoload": {
@@ -3351,7 +3455,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.3.2"
+ "source": "https://github.com/symfony/string/tree/v8.0.6"
},
"funding": [
{
@@ -3371,20 +3475,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:47:49+00:00"
+ "time": "2026-02-09T10:14:57+00:00"
},
{
"name": "theseer/tokenizer",
- "version": "1.2.3",
+ "version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
- "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
- "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
"shasum": ""
},
"require": {
@@ -3413,7 +3517,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
- "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
+ "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
},
"funding": [
{
@@ -3421,7 +3525,7 @@
"type": "github"
}
],
- "time": "2024-03-03T12:36:25+00:00"
+ "time": "2025-11-17T20:03:58+00:00"
},
{
"name": "webmozart/glob",
@@ -3475,13 +3579,13 @@
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=8.0",
+ "php": ">=8.2",
"ext-swoole": "*"
},
- "platform-dev": [],
- "plugin-api-version": "2.3.0"
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
}
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 b0f46a4b..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();
```
@@ -144,7 +132,7 @@ use Utopia\Http\Swoole\Response;
use Swoole\Http\Server;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
-use Utopia\Http\Validator\Wildcard;
+use Utopia\Validator\Wildcard;
$http = new Server("0.0.0.0", 8080);
@@ -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..d0128938 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/example/src/server.php b/example/src/server.php
index 4eb07f71..d05f80f2 100644
--- a/example/src/server.php
+++ b/example/src/server.php
@@ -5,7 +5,7 @@
use Utopia\Http\Http;
use Utopia\Http\Response;
use Utopia\Http\Adapter\Swoole\Server;
-use Utopia\Http\Validator\Text;
+use Utopia\Validator\Text;
Http::get('/')
->param('name', 'World', new Text(256), 'Name to greet. Optional, max length 256.', true)
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/Request.php b/src/Http/Adapter/FPM/Request.php
index 41452440..d095445f 100644
--- a/src/Http/Adapter/FPM/Request.php
+++ b/src/Http/Adapter/FPM/Request.php
@@ -108,7 +108,8 @@ public function getPort(): string
*/
public function getHostname(): string
{
- return (string) \parse_url($this->getProtocol().'://'.$this->getServer('HTTP_HOST', ''), PHP_URL_HOST);
+ $hostname = \parse_url($this->getProtocol().'://'.$this->getServer('HTTP_HOST', ''), PHP_URL_HOST);
+ return strtolower((string) ($hostname));
}
/**
diff --git a/src/Http/Adapter/FPM/Server.php b/src/Http/Adapter/FPM/Server.php
index e90f0cd7..75842551 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)
{
}
@@ -15,16 +15,22 @@ public function onRequest(callable $callback)
{
$request = new Request();
$response = new Response();
+ $resources = [
+ 'fpmRequest' => $request,
+ 'fpmResponse' => $response,
+ ];
- Http::setResource('fpmRequest', fn () => $request);
- Http::setResource('fpmResponse', fn () => $response);
-
- call_user_func($callback, $request, $response, 'fpm');
+ \call_user_func($callback, $request, $response, 'fpm', $resources);
}
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/Request.php b/src/Http/Adapter/Swoole/Request.php
index b07a5fdb..d5bd799c 100644
--- a/src/Http/Adapter/Swoole/Request.php
+++ b/src/Http/Adapter/Swoole/Request.php
@@ -122,7 +122,8 @@ public function getPort(): string
*/
public function getHostname(): string
{
- return strval(\parse_url($this->getProtocol().'://'.$this->getHeader('x-forwarded-host', $this->getHeader('host')), PHP_URL_HOST));
+ $hostname = \parse_url($this->getProtocol().'://'.$this->getHeader('x-forwarded-host', $this->getHeader('host')), PHP_URL_HOST);
+ return strtolower(strval($hostname));
}
/**
diff --git a/src/Http/Adapter/Swoole/Server.php b/src/Http/Adapter/Swoole/Server.php
index bb3b87d8..cdad6d2a 100755
--- a/src/Http/Adapter/Swoole/Server.php
+++ b/src/Http/Adapter/Swoole/Server.php
@@ -4,41 +4,50 @@
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());
+ Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] = new Container($this->container);
- Http::setResource('swooleRequest', fn () => $request, [], $context);
- Http::setResource('swooleResponse', fn () => $response, [], $context);
+ $utopiaRequest = new Request($request);
+ $utopiaResponse = new Response($response);
- call_user_func($callback, new Request($request), new Response($response), $context);
+ \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/Hook.php b/src/Http/Hook.php
index 2266da26..0f177dc3 100644
--- a/src/Http/Hook.php
+++ b/src/Http/Hook.php
@@ -2,6 +2,8 @@
namespace Utopia\Http;
+use Utopia\Validator;
+
class Hook
{
/**
diff --git a/src/Http/Http.php b/src/Http/Http.php
index 8fe02785..ee91be61 100755
--- a/src/Http/Http.php
+++ b/src/Http/Http.php
@@ -2,6 +2,9 @@
namespace Utopia\Http;
+use Utopia\DI\Container;
+use Utopia\Validator;
+
class Http
{
/**
@@ -30,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
@@ -349,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);
}
/**
@@ -572,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);
@@ -642,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();
@@ -652,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);
}
}
@@ -660,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);
}
}
@@ -681,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);
@@ -704,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);
@@ -715,9 +679,6 @@ public function execute(Route $route, Request $request, string $context): static
}
}
- // Reset resources for the context
- $this->resources[$context] = [];
-
return $this;
}
@@ -731,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
@@ -751,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);
}
}
@@ -760,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;
@@ -775,31 +736,20 @@ 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);
-
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);
@@ -819,11 +769,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;
@@ -835,26 +786,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()));
}
}
}
@@ -868,43 +819,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()));
}
}
}
@@ -912,6 +861,7 @@ public function run(Request $request, Response $response, string $context): stat
return $this;
}
+
/**
* Validate Param
*
@@ -924,7 +874,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;
@@ -933,7 +883,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
@@ -953,12 +903,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/Request.php b/src/Http/Request.php
index 8332d99d..c9762123 100755
--- a/src/Http/Request.php
+++ b/src/Http/Request.php
@@ -332,7 +332,16 @@ abstract public function removeHeader(string $key): static;
*/
public function getSize(): int
{
- return \mb_strlen(\implode("\n", $this->generateHeaders()), '8bit') + \mb_strlen(\file_get_contents('php://input'), '8bit');
+ $headers = $this->generateHeaders();
+ $headerStrings = [];
+ foreach ($headers as $key => $value) {
+ if (\is_array($value)) {
+ $headerStrings[] = $key . ': ' . \implode(', ', $value);
+ } else {
+ $headerStrings[] = $key . ': ' . $value;
+ }
+ }
+ return \mb_strlen(\implode("\n", $headerStrings), '8bit') + \mb_strlen(\file_get_contents('php://input'), '8bit');
}
/**
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/src/Http/Validator.php b/src/Http/Validator.php
deleted file mode 100755
index 6a8304ea..00000000
--- a/src/Http/Validator.php
+++ /dev/null
@@ -1,57 +0,0 @@
- $validators
- */
- public function __construct(protected array $validators, protected string $type = self::TYPE_MIXED)
- {
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- if (!(\is_null($this->failedRule))) {
- $description = $this->failedRule->getDescription();
- } else {
- $description = $this->validators[0]->getDescription();
- }
-
- return $description;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when all rules are valid if only one of the rules is invalid validation will fail.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- foreach ($this->validators as $rule) {
- $valid = $rule->isValid($value);
-
- if (!$valid) {
- $this->failedRule = $rule;
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->type;
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return true;
- }
-}
diff --git a/src/Http/Validator/AnyOf.php b/src/Http/Validator/AnyOf.php
deleted file mode 100644
index ff96a54c..00000000
--- a/src/Http/Validator/AnyOf.php
+++ /dev/null
@@ -1,87 +0,0 @@
- $validators
- */
- public function __construct(protected array $validators, protected string $type = self::TYPE_MIXED)
- {
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- if (!(\is_null($this->failedRule))) {
- $description = $this->failedRule->getDescription();
- } else {
- $description = $this->validators[0]->getDescription();
- }
-
- return $description;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when all rules are valid if only one of the rules is invalid validation will fail.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- foreach ($this->validators as $rule) {
- $valid = $rule->isValid($value);
-
- $this->failedRule = $rule;
-
- if ($valid) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->type;
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return true;
- }
-}
diff --git a/src/Http/Validator/ArrayList.php b/src/Http/Validator/ArrayList.php
deleted file mode 100644
index c2c7e5ab..00000000
--- a/src/Http/Validator/ArrayList.php
+++ /dev/null
@@ -1,116 +0,0 @@
-validator = $validator;
- $this->length = $length;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- $msg = 'Value must a valid array';
-
- if ($this->length > 0) {
- $msg .= ' no longer than ' . $this->length . ' items';
- }
-
- return $msg . ' and ' . $this->validator->getDescription();
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return true;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->validator->getType();
- }
-
- /**
- * Get Nested Validator
- *
- * @return Validator
- */
- public function getValidator(): Validator
- {
- return $this->validator;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is valid array and validator is valid.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- if (!\is_array($value)) {
- return false;
- }
-
- if ($this->length && \count($value) > $this->length) {
- return false;
- }
-
- foreach ($value as $element) {
- if (!$this->validator->isValid($element)) {
- return false;
- }
- }
-
- return true;
- }
-}
diff --git a/src/Http/Validator/Assoc.php b/src/Http/Validator/Assoc.php
deleted file mode 100644
index 9b7697e4..00000000
--- a/src/Http/Validator/Assoc.php
+++ /dev/null
@@ -1,88 +0,0 @@
-length = $length;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be a valid object.';
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return true;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_ARRAY;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is valid assoc array.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid($value): bool
- {
- if (!\is_array($value)) {
- return false;
- }
-
- $jsonString = \json_encode($value);
- $jsonStringSize = \strlen($jsonString);
-
- if ($jsonStringSize > $this->length) {
- return false;
- }
-
- return \array_keys($value) !== \range(0, \count($value) - 1);
- }
-}
diff --git a/src/Http/Validator/Boolean.php b/src/Http/Validator/Boolean.php
deleted file mode 100644
index 38b498ec..00000000
--- a/src/Http/Validator/Boolean.php
+++ /dev/null
@@ -1,94 +0,0 @@
-loose = $loose;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be a valid boolean';
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_BOOLEAN;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value has a boolean value.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid($value): bool
- {
- if ($this->loose && ($value === 'true' || $value === 'false')) { // Accept strings
- return true;
- }
-
- if ($this->loose && ($value === '1' || $value === '0')) { // Accept numeric strings
- return true;
- }
-
- if ($this->loose && ($value === 1 || $value === 0)) { // Accept integers
- return true;
- }
-
- if (\is_bool($value)) {
- return true;
- }
-
- return false;
- }
-}
diff --git a/src/Http/Validator/Domain.php b/src/Http/Validator/Domain.php
deleted file mode 100644
index dd213191..00000000
--- a/src/Http/Validator/Domain.php
+++ /dev/null
@@ -1,78 +0,0 @@
-loose = $loose;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be a valid float';
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_FLOAT;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is float.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- if ($this->loose) {
- if (!\is_numeric($value)) {
- return false;
- }
- $value = $value + 0;
- }
- if (!\is_float($value) && !\is_int($value)) {
- return false;
- }
-
- return true;
- }
-}
diff --git a/src/Http/Validator/HexColor.php b/src/Http/Validator/HexColor.php
deleted file mode 100644
index fe9d5926..00000000
--- a/src/Http/Validator/HexColor.php
+++ /dev/null
@@ -1,53 +0,0 @@
-whitelist = $whitelist;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'URL host must be one of: ' . \implode(', ', $this->whitelist);
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value starts with one of the given hosts
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid($value): bool
- {
- $urlValidator = new URL();
-
- if (!$urlValidator->isValid($value)) {
- return false;
- }
-
- $hostnameValidator = new Hostname($this->whitelist);
-
- return $hostnameValidator->isValid(\parse_url($value, PHP_URL_HOST));
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_STRING;
- }
-}
diff --git a/src/Http/Validator/Hostname.php b/src/Http/Validator/Hostname.php
deleted file mode 100644
index d3231f10..00000000
--- a/src/Http/Validator/Hostname.php
+++ /dev/null
@@ -1,114 +0,0 @@
-allowList = $allowList;
- }
-
- /**
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be a valid hostname without path, port and protocol.';
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_STRING;
- }
-
- /**
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- // Validate proper format
- if (!\is_string($value) || empty($value)) {
- return false;
- }
-
- // Max length 253 chars: https://en.wikipedia.org/wiki/Hostname#:~:text=The%20entire%20hostname%2C%20including%20the,maximum%20of%20253%20ASCII%20characters
- if (\mb_strlen($value) > 253) {
- return false;
- }
-
- // This tests: 'http://', 'https://', and 'myapp.com/route'
- if (\str_contains($value, '/')) {
- return false;
- }
-
- // This tests for: 'myapp.com:3000'
- if (\str_contains($value, ':')) {
- return false;
- }
-
- // Logic #1: Empty allowList means everything is allowed
- if (empty($this->allowList)) {
- return true;
- }
-
- // Logic #2: Allow List not empty, there are rules to check
- // Loop through all allowed hostnames until match is found
- foreach ($this->allowList as $allowedHostname) {
- // If exact match; allow
- // If *, allow everything
- if ($value === $allowedHostname || $allowedHostname === '*') {
- return true;
- }
-
- // If wildcard symbol used
- if (\str_starts_with($allowedHostname, '*')) {
- // Remove starting * symbol before comparing
- $allowedHostname = substr($allowedHostname, 1);
-
- // If rest of hostname match; allow
- // Notice allowedHostname still includes starting dot. Root domain is NOT allowed by wildcard.
- if (\str_ends_with($value, $allowedHostname)) {
- return true;
- }
- }
- }
-
- // If finished loop above without result, match is not found
- return false;
- }
-}
diff --git a/src/Http/Validator/IP.php b/src/Http/Validator/IP.php
deleted file mode 100644
index e0ffef49..00000000
--- a/src/Http/Validator/IP.php
+++ /dev/null
@@ -1,113 +0,0 @@
-type = $type;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be a valid IP address';
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is valid IP address.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid($value): bool
- {
- switch ($this->type) {
- case self::ALL:
- if (\filter_var($value, FILTER_VALIDATE_IP)) {
- return true;
- }
- break;
-
- case self::V4:
- if (\filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
- return true;
- }
- break;
-
- case self::V6:
- if (\filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
- return true;
- }
- break;
-
- default:
- return false;
- }
-
- return false;
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_STRING;
- }
-}
diff --git a/src/Http/Validator/Integer.php b/src/Http/Validator/Integer.php
deleted file mode 100755
index 76af2aa4..00000000
--- a/src/Http/Validator/Integer.php
+++ /dev/null
@@ -1,88 +0,0 @@
-loose = $loose;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be a valid integer';
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_INTEGER;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is integer.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- if ($this->loose) {
- if (!\is_numeric($value)) {
- return false;
- }
- $value = $value + 0;
- }
- if (!\is_int($value)) {
- return false;
- }
-
- return true;
- }
-}
diff --git a/src/Http/Validator/JSON.php b/src/Http/Validator/JSON.php
deleted file mode 100644
index 5bb734f6..00000000
--- a/src/Http/Validator/JSON.php
+++ /dev/null
@@ -1,59 +0,0 @@
- $validators
- */
- public function __construct(protected array $validators, protected string $type = self::TYPE_MIXED)
- {
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- $description = '';
-
- if (!(\is_null($this->failedRule))) {
- $description = $this->failedRule->getDescription();
- } else {
- $description = $this->validators[0]->getDescription();
- }
-
- return $description;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when all rules are valid if only one of the rules is invalid validation will fail.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- foreach ($this->validators as $rule) {
- $valid = $rule->isValid($value);
-
- if ($valid) {
- $this->failedRule = $rule;
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->type;
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return true;
- }
-}
diff --git a/src/Http/Validator/Nullable.php b/src/Http/Validator/Nullable.php
deleted file mode 100644
index b6c0007b..00000000
--- a/src/Http/Validator/Nullable.php
+++ /dev/null
@@ -1,73 +0,0 @@
-validator->getDescription() . ' or null';
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->validator->getType();
- }
-
- /**
- * @return Validator
- */
- public function getValidator(): Validator
- {
- return $this->validator;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is text with valid length.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- if (\is_null($value)) {
- return true;
- }
-
- return $this->validator->isValid($value);
- }
-}
diff --git a/src/Http/Validator/Numeric.php b/src/Http/Validator/Numeric.php
deleted file mode 100755
index 13c88610..00000000
--- a/src/Http/Validator/Numeric.php
+++ /dev/null
@@ -1,66 +0,0 @@
-min = $min;
- $this->max = $max;
- $this->format = $format;
- }
-
- /**
- * Get Range Minimum Value
- *
- * @return int|float
- */
- public function getMin(): int|float
- {
- return $this->min;
- }
-
- /**
- * Get Range Maximum Value
- *
- * @return int|float
- */
- public function getMax(): int|float
- {
- return $this->max;
- }
-
- /**
- * Get Range Format
- *
- * @return string
- */
- public function getFormat(): string
- {
- return $this->format;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be a valid range between '.\number_format($this->min).' and '.\number_format($this->max);
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->format;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value number is bigger or equal than $min number and lower or equal than $max.
- * Not strict, considers any valid integer to be a valid float
- * Considers infinity to be a valid integer
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- if (!parent::isValid($value)) {
- return false;
- }
-
- switch ($this->format) {
- case self::TYPE_INTEGER:
- // Accept infinity as an integer
- // Since gettype(INF) === TYPE_FLOAT
- if ($value === INF || $value === -INF) {
- break; // move to check if value is within range
- }
- $value = $value + 0;
- if (!is_int($value)) {
- return false;
- }
- break;
- case self::TYPE_FLOAT:
- if (!is_numeric($value)) {
- return false;
- }
- $value = $value + 0.0;
- break;
- default:
- return false;
- }
-
- if ($this->min <= $value && $this->max >= $value) {
- return true;
- }
-
- return false;
- }
-}
diff --git a/src/Http/Validator/Text.php b/src/Http/Validator/Text.php
deleted file mode 100644
index 4df5d7e4..00000000
--- a/src/Http/Validator/Text.php
+++ /dev/null
@@ -1,138 +0,0 @@
-length = $length;
- $this->min = $min;
- $this->allowList = $allowList;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- $message = 'Value must be a valid string';
-
- if ($this->min === $this->length) {
- $message .= ' and exactly '.$this->length.' chars';
- } else {
- if ($this->min) {
- $message .= ' and at least '.$this->min.' chars';
- }
-
- if ($this->length) {
- $message .= ' and no longer than '.$this->length.' chars';
- }
- }
-
- if ($this->allowList) {
- $message .= ' and only consist of \''.\implode(', ', $this->allowList).'\' chars';
- }
-
- return $message;
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_STRING;
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is text with valid length.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- if (!\is_string($value)) {
- return false;
- }
-
- if (\mb_strlen($value) < $this->min) {
- return false;
- }
-
- if (\mb_strlen($value) > $this->length && $this->length !== 0) {
- return false;
- }
-
- if (\count($this->allowList) > 0) {
- foreach (\str_split($value) as $char) {
- if (!\in_array($char, $this->allowList)) {
- return false;
- }
- }
- }
-
- return true;
- }
-}
diff --git a/src/Http/Validator/URL.php b/src/Http/Validator/URL.php
deleted file mode 100644
index f6434e98..00000000
--- a/src/Http/Validator/URL.php
+++ /dev/null
@@ -1,86 +0,0 @@
-allowedSchemes = $allowedSchemes;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- if (!empty($this->allowedSchemes)) {
- return 'Value must be a valid URL with following schemes (' . \implode(', ', $this->allowedSchemes) . ')';
- }
-
- return 'Value must be a valid URL';
- }
-
- /**
- * Is valid
- *
- * Validation will pass when $value is valid URL.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid($value): bool
- {
- if (\filter_var($value, FILTER_VALIDATE_URL) === false) {
- return false;
- }
-
- if (!empty($this->allowedSchemes) && !\in_array(\parse_url($value, PHP_URL_SCHEME), $this->allowedSchemes)) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return self::TYPE_STRING;
- }
-}
diff --git a/src/Http/Validator/WhiteList.php b/src/Http/Validator/WhiteList.php
deleted file mode 100755
index 53a6bb16..00000000
--- a/src/Http/Validator/WhiteList.php
+++ /dev/null
@@ -1,119 +0,0 @@
-list = $list;
- $this->strict = $strict;
- $this->type = $type;
-
- if (!$this->strict) {
- foreach ($this->list as $key => &$value) {
- $this->list[$key] = \strtolower($value);
- }
- }
- }
-
- /**
- * Get List of All Allowed Values
- *
- * @return array
- */
- public function getList(): array
- {
- return $this->list;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- return 'Value must be one of ('.\implode(', ', $this->list).')';
- }
-
- /**
- * Is array
- *
- * Function will return true if object is array.
- *
- * @return bool
- */
- public function isArray(): bool
- {
- return false;
- }
-
- /**
- * Get Type
- *
- * Returns validator type.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->type;
- }
-
- /**
- * Is valid
- *
- * Validation will pass if $value is in the white list array.
- *
- * @param mixed $value
- * @return bool
- */
- public function isValid(mixed $value): bool
- {
- if (\is_array($value)) {
- return false;
- }
-
- $value = ($this->strict) ? $value : \strtolower($value);
-
- if (!\in_array($value, $this->list, $this->strict)) {
- return false;
- }
-
- return true;
- }
-}
diff --git a/src/Http/Validator/Wildcard.php b/src/Http/Validator/Wildcard.php
deleted file mode 100644
index 1e7314a8..00000000
--- a/src/Http/Validator/Wildcard.php
+++ /dev/null
@@ -1,62 +0,0 @@
-http = new Http(new Server(), 'Asia/Tel_Aviv');
+ $this->container = $this->http->getContainer();
$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()
@@ -174,7 +113,7 @@ public function testCanExecuteRoute(): void
\ob_end_clean();
// With Params
- $resource = $this->http->getResource('rand', '1');
+ $resource = $this->container->get('rand');
$route = new Route('GET', '/path');
$route
@@ -200,7 +139,7 @@ public function testCanExecuteRoute(): void
$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
@@ -220,7 +159,7 @@ public function testCanExecuteRoute(): void
$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')
@@ -291,7 +230,7 @@ public function testCanExecuteRoute(): void
$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']);
@@ -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);
});
diff --git a/tests/RequestTest.php b/tests/RequestTest.php
index 55d49ee9..02d8930a 100755
--- a/tests/RequestTest.php
+++ b/tests/RequestTest.php
@@ -313,4 +313,23 @@ public function testCanGetRange()
$this->assertEquals(null, $this->request->getRangeStart());
$this->assertEquals(null, $this->request->getRangeEnd());
}
+
+ public function testCanGetSizeWithArrayHeaders()
+ {
+ $this->request->addHeader('content-type', 'application/json');
+
+ $reflection = new \ReflectionClass($this->request);
+ $headersProperty = $reflection->getProperty('headers');
+ $headersProperty->setAccessible(true);
+
+ $headers = $headersProperty->getValue($this->request) ?? [];
+ $headers['accept'] = ['application/json', 'text/html'];
+ $headers['x-custom'] = ['value1', 'value2', 'value3'];
+ $headersProperty->setValue($this->request, $headers);
+
+ $size = $this->request->getSize();
+
+ $this->assertIsInt($size);
+ $this->assertGreaterThan(0, $size);
+ }
}
diff --git a/tests/RouteTest.php b/tests/RouteTest.php
index 4a966b87..ea48e111 100755
--- a/tests/RouteTest.php
+++ b/tests/RouteTest.php
@@ -3,7 +3,7 @@
namespace Utopia\Http;
use PHPUnit\Framework\TestCase;
-use Utopia\Http\Validator\Text;
+use Utopia\Validator\Text;
class RouteTest extends TestCase
{
diff --git a/tests/Validator/ArrayListTest.php b/tests/Validator/ArrayListTest.php
deleted file mode 100755
index d24c65c0..00000000
--- a/tests/Validator/ArrayListTest.php
+++ /dev/null
@@ -1,52 +0,0 @@
-assertFalse($arrayList->isValid(['text']));
- $this->assertEquals('Value must a valid array and Value must be a valid integer', $arrayList->getDescription());
-
- $arrayList = new ArrayList(new Integer(), 3);
- $this->assertFalse($arrayList->isValid(['a', 'b', 'c', 'd']));
- $this->assertEquals('Value must a valid array no longer than 3 items and Value must be a valid integer', $arrayList->getDescription());
- }
-
- public function testCanValidateTextValues(): void
- {
- $arrayList = new ArrayList(new Text(100));
- $this->assertTrue($arrayList->isArray(), 'true');
- $this->assertTrue($arrayList->isValid([0 => 'string', 1 => 'string']));
- $this->assertTrue($arrayList->isValid(['string', 'string']));
- $this->assertFalse($arrayList->isValid(['string', 'string', 3]));
- $this->assertFalse($arrayList->isValid('string'));
- $this->assertFalse($arrayList->isValid('string'));
- $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $arrayList->getType());
- $this->assertInstanceOf(Text::class, $arrayList->getValidator());
- }
-
- public function testCanValidateNumericValues(): void
- {
- $arrayList = new ArrayList(new Numeric());
- $this->assertTrue($arrayList->isValid([1, 2, 3]));
- $this->assertFalse($arrayList->isValid(1));
- $this->assertFalse($arrayList->isValid('string'));
- $this->assertEquals(\Utopia\Http\Validator::TYPE_MIXED, $arrayList->getType());
- $this->assertInstanceOf(Numeric::class, $arrayList->getValidator());
- }
-
- public function testCanValidateNumericValuesWithBoundaries(): void
- {
- $arrayList = new ArrayList(new Numeric(), 2);
- $this->assertTrue($arrayList->isValid([1]));
- $this->assertTrue($arrayList->isValid([1, 2]));
- $this->assertFalse($arrayList->isValid([1, 2, 3]));
- $this->assertEquals($arrayList->getType(), \Utopia\Http\Validator::TYPE_MIXED);
- $this->assertInstanceOf(Numeric::class, $arrayList->getValidator());
- }
-}
diff --git a/tests/Validator/AssocTest.php b/tests/Validator/AssocTest.php
deleted file mode 100755
index 2036998b..00000000
--- a/tests/Validator/AssocTest.php
+++ /dev/null
@@ -1,43 +0,0 @@
-assoc = new Assoc();
- }
-
- public function tearDown(): void
- {
- $this->assoc = null;
- }
-
- public function testCanValidateAssocArray(): void
- {
- $this->assertTrue($this->assoc->isValid(['1' => 'a', '0' => 'b', '2' => 'c']));
- $this->assertTrue($this->assoc->isValid(['a' => 'a', 'b' => 'b', 'c' => 'c']));
- $this->assertTrue($this->assoc->isValid([]));
- $this->assertTrue($this->assoc->isValid(['value' => str_repeat('-', 62000)]));
- $this->assertTrue($this->assoc->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_ARRAY, $this->assoc->getType());
- }
-
- public function testCantValidateSequentialArray(): void
- {
- $this->assertFalse($this->assoc->isValid([0 => 'string', 1 => 'string']));
- $this->assertFalse($this->assoc->isValid(['a']));
- $this->assertFalse($this->assoc->isValid(['a', 'b', 'c']));
- $this->assertFalse($this->assoc->isValid(['0' => 'a', '1' => 'b', '2' => 'c']));
- }
-
- public function testCantValidateAssocArrayWithOver65kCharacters(): void
- {
- $this->assertFalse($this->assoc->isValid(['value' => str_repeat('-', 66000)]));
- }
-}
diff --git a/tests/Validator/BooleanTest.php b/tests/Validator/BooleanTest.php
deleted file mode 100755
index bda71580..00000000
--- a/tests/Validator/BooleanTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-assertTrue($boolean->isValid(true));
- $this->assertTrue($boolean->isValid(false));
- $this->assertFalse($boolean->isValid('false'));
- $this->assertFalse($boolean->isValid('true'));
- $this->assertFalse($boolean->isValid('0'));
- $this->assertFalse($boolean->isValid('1'));
- $this->assertFalse($boolean->isValid(0));
- $this->assertFalse($boolean->isValid(1));
- $this->assertFalse($boolean->isValid(['string', 'string']));
- $this->assertFalse($boolean->isValid('string'));
- $this->assertFalse($boolean->isValid(1.2));
- $this->assertFalse($boolean->isArray());
- $this->assertEquals($boolean->getType(), \Utopia\Http\Validator::TYPE_BOOLEAN);
- }
-
- public function testCanValidateLoosely()
- {
- $boolean = new Boolean(true);
-
- $this->assertTrue($boolean->isValid(true));
- $this->assertTrue($boolean->isValid(false));
- $this->assertTrue($boolean->isValid('false'));
- $this->assertTrue($boolean->isValid('true'));
- $this->assertTrue($boolean->isValid('0'));
- $this->assertTrue($boolean->isValid('1'));
- $this->assertTrue($boolean->isValid(0));
- $this->assertTrue($boolean->isValid(1));
- $this->assertFalse($boolean->isValid(['string', 'string']));
- $this->assertFalse($boolean->isValid('string'));
- $this->assertFalse($boolean->isValid(1.2));
- $this->assertFalse($boolean->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_BOOLEAN, $boolean->getType());
- }
-}
diff --git a/tests/Validator/DomainTest.php b/tests/Validator/DomainTest.php
deleted file mode 100644
index 85a346e1..00000000
--- a/tests/Validator/DomainTest.php
+++ /dev/null
@@ -1,37 +0,0 @@
-domain = new Domain();
- }
-
- public function testIsValid()
- {
- // Assertions
- $this->assertEquals(true, $this->domain->isValid('example.com'));
- $this->assertEquals(true, $this->domain->isValid('subdomain.example.com'));
- $this->assertEquals(true, $this->domain->isValid('subdomain.example-app.com'));
- $this->assertEquals(true, $this->domain->isValid('subdomain.example_app.com'));
- $this->assertEquals(true, $this->domain->isValid('subdomain-new.example.com'));
- $this->assertEquals(true, $this->domain->isValid('subdomain_new.example.com'));
- $this->assertEquals(true, $this->domain->isValid('localhost'));
- $this->assertEquals(true, $this->domain->isValid('example.io'));
- $this->assertEquals(true, $this->domain->isValid('example.org'));
- $this->assertEquals(true, $this->domain->isValid('example.org'));
- $this->assertEquals(false, $this->domain->isValid(false));
- $this->assertEquals(false, $this->domain->isValid('.'));
- $this->assertEquals(false, $this->domain->isValid('..'));
- $this->assertEquals(false, $this->domain->isValid(''));
- $this->assertEquals(false, $this->domain->isValid(['string', 'string']));
- $this->assertEquals(false, $this->domain->isValid(1));
- $this->assertEquals(false, $this->domain->isValid(1.2));
- }
-}
diff --git a/tests/Validator/FloatValidatorTest.php b/tests/Validator/FloatValidatorTest.php
deleted file mode 100755
index be905b68..00000000
--- a/tests/Validator/FloatValidatorTest.php
+++ /dev/null
@@ -1,39 +0,0 @@
-assertTrue($validator->isValid(27.25));
- $this->assertTrue($validator->isValid(23));
- $this->assertTrue($validator->isValid(23.5));
- $this->assertTrue($validator->isValid(1e7));
- $this->assertFalse($validator->isValid('abc'));
- $this->assertFalse($validator->isValid(true));
- $this->assertFalse($validator->isValid('23.5'));
- $this->assertFalse($validator->isValid('23'));
- $this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $validator->getType());
- }
-
- public function testCanValidateLoosely(): void
- {
- $validator = new FloatValidator(true);
-
- $this->assertTrue($validator->isValid(27.25));
- $this->assertTrue($validator->isValid(23));
- $this->assertTrue($validator->isValid(23.5));
- $this->assertTrue($validator->isValid(1e7));
- $this->assertTrue($validator->isValid('23.5'));
- $this->assertTrue($validator->isValid('23'));
- $this->assertFalse($validator->isValid('abc'));
- $this->assertFalse($validator->isValid(true));
- $this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $validator->getType());
- }
-}
diff --git a/tests/Validator/HexColorTest.php b/tests/Validator/HexColorTest.php
deleted file mode 100755
index f90d92fe..00000000
--- a/tests/Validator/HexColorTest.php
+++ /dev/null
@@ -1,26 +0,0 @@
-assertTrue($hexColor->isValid('000'));
- $this->assertTrue($hexColor->isValid('ffffff'));
- $this->assertTrue($hexColor->isValid('fff'));
- $this->assertTrue($hexColor->isValid('000000'));
-
- $this->assertFalse($hexColor->isValid('AB10BC99'));
- $this->assertFalse($hexColor->isValid('AR1012'));
- $this->assertFalse($hexColor->isValid('ab12bc99'));
- $this->assertFalse($hexColor->isValid('00'));
- $this->assertFalse($hexColor->isValid('ffff'));
- $this->assertFalse($hexColor->isArray());
-
- $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $hexColor->getType());
- }
-}
diff --git a/tests/Validator/HostTest.php b/tests/Validator/HostTest.php
deleted file mode 100644
index 4162a5b3..00000000
--- a/tests/Validator/HostTest.php
+++ /dev/null
@@ -1,30 +0,0 @@
-host = new Host(['example.io', 'subdomain.example.test', 'localhost', '*.appwrite.io']);
- }
-
- public function testIsValid()
- {
- // Assertions
- $this->assertEquals($this->host->isValid('https://example.io/link'), true);
- $this->assertEquals($this->host->isValid('https://localhost'), true);
- $this->assertEquals($this->host->isValid('localhost'), false);
- $this->assertEquals($this->host->isValid('http://subdomain.example.test/path'), true);
- $this->assertEquals($this->host->isValid('http://test.subdomain.example.test/path'), false);
- $this->assertEquals($this->host->isValid('http://appwrite.io/path'), false);
- $this->assertEquals($this->host->isValid('http://me.appwrite.io/path'), true);
- $this->assertEquals($this->host->isValid('http://you.appwrite.io/path'), true);
- $this->assertEquals($this->host->isValid('http://us.together.appwrite.io/path'), true);
- $this->assertEquals($this->host->getType(), 'string');
- }
-}
diff --git a/tests/Validator/HostnameTest.php b/tests/Validator/HostnameTest.php
deleted file mode 100755
index 2760648d..00000000
--- a/tests/Validator/HostnameTest.php
+++ /dev/null
@@ -1,106 +0,0 @@
-assertEquals(\Utopia\Http\Validator::TYPE_STRING, $validator->getType());
- $this->assertFalse($validator->isArray());
-
- $this->assertTrue($validator->isValid('myweb.com'));
- $this->assertTrue($validator->isValid('httpmyweb.com'));
- $this->assertTrue($validator->isValid('httpsmyweb.com'));
- $this->assertTrue($validator->isValid('wsmyweb.com'));
- $this->assertTrue($validator->isValid('wssmyweb.com'));
- $this->assertTrue($validator->isValid('vercel.app'));
- $this->assertTrue($validator->isValid('web.vercel.app'));
- $this->assertTrue($validator->isValid('my-web.vercel.app'));
- $this->assertTrue($validator->isValid('my-project.my-web.vercel.app'));
- $this->assertTrue($validator->isValid('my-commit.my-project.my-web.vercel.app'));
- $this->assertTrue($validator->isValid('myapp.co.uk'));
- $this->assertTrue($validator->isValid('*.myapp.com'));
- $this->assertTrue($validator->isValid('myapp.*'));
-
- $this->assertFalse($validator->isValid('https://myweb.com'));
- $this->assertFalse($validator->isValid('ws://myweb.com'));
- $this->assertFalse($validator->isValid('wss://myweb.com'));
- $this->assertFalse($validator->isValid('http://myweb.com'));
- $this->assertFalse($validator->isValid('http://myweb.com:3000'));
- $this->assertFalse($validator->isValid('http://myweb.com/blog'));
- $this->assertFalse($validator->isValid('myweb.com:80'));
- $this->assertFalse($validator->isValid('myweb.com:3000'));
- $this->assertFalse($validator->isValid('myweb.com/blog'));
- $this->assertFalse($validator->isValid('myweb.com/blog/article1'));
-
- // Max length test
- $domain = \str_repeat('bestdomain', 25); // 250 chars total
-
- $domain .= '.sk'; // Exactly at the limit
- $this->assertTrue($validator->isValid($domain));
-
- $domain .= 'a'; // Exactly over the limit
- $this->assertFalse($validator->isValid($domain));
- }
-
- public function testCanValidateHostnamesWithAllowList(): void
- {
- // allowList tests
- $validator = new Hostname([
- 'myweb.vercel.app',
- 'myweb.com',
- '*.myapp.com',
- ]);
-
- $this->assertTrue($validator->isValid('myweb.vercel.app'));
- $this->assertFalse($validator->isValid('myweb.vercel.com'));
- $this->assertFalse($validator->isValid('myweb2.vercel.app'));
- $this->assertFalse($validator->isValid('vercel.app'));
- $this->assertFalse($validator->isValid('mycommit.myweb.vercel.app'));
-
- $this->assertTrue($validator->isValid('myweb.com'));
- $this->assertFalse($validator->isValid('myweb.eu'));
- $this->assertFalse($validator->isValid('project.myweb.eu'));
- $this->assertFalse($validator->isValid('commit.project.myweb.eu'));
-
- $this->assertTrue($validator->isValid('project1.myapp.com'));
- $this->assertTrue($validator->isValid('project2.myapp.com'));
- $this->assertTrue($validator->isValid('project-with-dash.myapp.com'));
- $this->assertTrue($validator->isValid('anything.myapp.com'));
- $this->assertTrue($validator->isValid('commit.anything.myapp.com'));
- $this->assertFalse($validator->isValid('anything.myapp.eu'));
- $this->assertFalse($validator->isValid('myapp.com'));
-
- $validator = new Hostname(['localhost']);
- $this->assertTrue($validator->isValid('localhost'));
- }
-
- public function testCanValidateHostnamesWithWildcard(): void
- {
- $validator = new Hostname();
- $this->assertTrue($validator->isValid('*'));
-
- $validator = new Hostname(['netlify.*']);
- $this->assertFalse($validator->isValid('netlify.com'));
- $this->assertFalse($validator->isValid('netlify.eu'));
- $this->assertFalse($validator->isValid('netlify.app'));
-
- $validator = new Hostname(['*.*.app.io']);
- $this->assertFalse($validator->isValid('app.io'));
- $this->assertFalse($validator->isValid('project.app.io'));
- $this->assertFalse($validator->isValid('commit.project.app.io'));
- $this->assertFalse($validator->isValid('api.commit.project.app.io'));
-
- $validator = new Hostname(['*']);
- $this->assertTrue($validator->isValid('*'));
- $this->assertTrue($validator->isValid('localhost'));
- $this->assertTrue($validator->isValid('anything')); // Like localhost
- $this->assertTrue($validator->isValid('anything.com'));
- $this->assertTrue($validator->isValid('anything.with.subdomains.eu'));
- }
-}
diff --git a/tests/Validator/IPTest.php b/tests/Validator/IPTest.php
deleted file mode 100644
index 074a8f68..00000000
--- a/tests/Validator/IPTest.php
+++ /dev/null
@@ -1,67 +0,0 @@
-assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
- $this->assertEquals($validator->isValid('109.67.204.101'), true);
- $this->assertEquals($validator->isValid(23.5), false);
- $this->assertEquals($validator->isValid('23.5'), false);
- $this->assertEquals($validator->isValid(null), false);
- $this->assertEquals($validator->isValid(true), false);
- $this->assertEquals($validator->isValid(false), false);
- $this->assertEquals($validator->getType(), 'string');
- }
-
- public function testIsValidIPALL()
- {
- $validator = new IP(IP::ALL);
-
- // Assertions
- $this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
- $this->assertEquals($validator->isValid('109.67.204.101'), true);
- $this->assertEquals($validator->isValid(23.5), false);
- $this->assertEquals($validator->isValid('23.5'), false);
- $this->assertEquals($validator->isValid(null), false);
- $this->assertEquals($validator->isValid(true), false);
- $this->assertEquals($validator->isValid(false), false);
- }
-
- public function testIsValidIPV4()
- {
- $validator = new IP(IP::V4);
-
- // Assertions
- $this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), false);
- $this->assertEquals($validator->isValid('109.67.204.101'), true);
- $this->assertEquals($validator->isValid(23.5), false);
- $this->assertEquals($validator->isValid('23.5'), false);
- $this->assertEquals($validator->isValid(null), false);
- $this->assertEquals($validator->isValid(true), false);
- $this->assertEquals($validator->isValid(false), false);
- }
-
- public function testIsValidIPV6()
- {
- $validator = new IP(IP::V6);
-
- // Assertions
- $this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
- $this->assertEquals($validator->isValid('109.67.204.101'), false);
- $this->assertEquals($validator->isValid(23.5), false);
- $this->assertEquals($validator->isValid('23.5'), false);
- $this->assertEquals($validator->isValid(null), false);
- $this->assertEquals($validator->isValid(true), false);
- $this->assertEquals($validator->isValid(false), false);
- }
-}
diff --git a/tests/Validator/IntegerTest.php b/tests/Validator/IntegerTest.php
deleted file mode 100755
index faddca2c..00000000
--- a/tests/Validator/IntegerTest.php
+++ /dev/null
@@ -1,36 +0,0 @@
-assertTrue($validator->isValid(23));
- $this->assertFalse($validator->isValid('23'));
- $this->assertFalse($validator->isValid(23.5));
- $this->assertFalse($validator->isValid('23.5'));
- $this->assertFalse($validator->isValid(null));
- $this->assertFalse($validator->isValid(true));
- $this->assertFalse($validator->isValid(false));
- $this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $validator->getType());
- }
-
- public function testCanValidateLoosely()
- {
- $validator = new Integer(true);
- $this->assertTrue($validator->isValid(23));
- $this->assertTrue($validator->isValid('23'));
- $this->assertFalse($validator->isValid(23.5));
- $this->assertFalse($validator->isValid('23.5'));
- $this->assertFalse($validator->isValid(null));
- $this->assertFalse($validator->isValid(true));
- $this->assertFalse($validator->isValid(false));
- $this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $validator->getType());
- }
-}
diff --git a/tests/Validator/JSONTest.php b/tests/Validator/JSONTest.php
deleted file mode 100755
index d4a4e358..00000000
--- a/tests/Validator/JSONTest.php
+++ /dev/null
@@ -1,28 +0,0 @@
-assertTrue($json->isValid('{}'));
- $this->assertTrue($json->isValid([]));
- $this->assertTrue($json->isValid(['test']));
- $this->assertTrue($json->isValid(['test' => 'demo']));
- $this->assertTrue($json->isValid('{"test": "demo"}'));
-
- $this->assertFalse($json->isValid(''));
- $this->assertFalse($json->isValid(false));
- $this->assertFalse($json->isValid(null));
- $this->assertFalse($json->isValid('string'));
- $this->assertFalse($json->isValid(1));
- $this->assertFalse($json->isValid(1.2));
- $this->assertFalse($json->isValid("{'test': 'demo'}"));
- $this->assertFalse($json->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_OBJECT, $json->getType());
- }
-}
diff --git a/tests/Validator/MultipleOfTest.php b/tests/Validator/MultipleOfTest.php
deleted file mode 100644
index 06435aa8..00000000
--- a/tests/Validator/MultipleOfTest.php
+++ /dev/null
@@ -1,61 +0,0 @@
-assertEquals('string', $validator->getType());
- $this->assertEquals("Value must be a valid string and at least 1 chars and no longer than 20 chars", $validator->getDescription());
-
- // Valid URL but invalid text length
- $this->assertFalse($validator->isValid('http://example.com/very-long-url'));
-
- // Valid text within length, but invalid URL
- $this->assertFalse($validator->isValid('hello world'));
-
- // Both conditions satisfied
- $this->assertTrue($validator->isValid('http://example.com'));
- $this->assertTrue($validator->isValid('https://google.com'));
-
- // Neither condition satisfied
- $this->assertFalse($validator->isValid('example.com/hello-world'));
- $this->assertFalse($validator->isValid(''));
- }
-
- public function testRules()
- {
- $validTextValidUrl = 'http://example.com';
- $validTextInvalidUrl = 'hello world';
- $invalidTextValidUrl = 'http://example.com/very-long-url';
- $invalidTextInvalidUrl = 'Some very long text that is also not an URL';
-
- $vaidator = new AllOf([new Text(20), new URL()], Validator::TYPE_STRING);
- $this->assertTrue($vaidator->isValid($validTextValidUrl));
- $this->assertFalse($vaidator->isValid($validTextInvalidUrl));
- $this->assertFalse($vaidator->isValid($invalidTextValidUrl));
- $this->assertFalse($vaidator->isValid($invalidTextInvalidUrl));
-
- $vaidator = new AnyOf([new Text(20), new URL()], Validator::TYPE_STRING);
- $this->assertTrue($vaidator->isValid($validTextValidUrl));
- $this->assertTrue($vaidator->isValid($validTextInvalidUrl));
- $this->assertTrue($vaidator->isValid($invalidTextValidUrl));
- $this->assertFalse($vaidator->isValid($invalidTextInvalidUrl));
-
- $vaidator = new NoneOf([new Text(20), new URL()], Validator::TYPE_STRING);
- $this->assertFalse($vaidator->isValid($validTextValidUrl));
- $this->assertFalse($vaidator->isValid($validTextInvalidUrl));
- $this->assertFalse($vaidator->isValid($invalidTextValidUrl));
- $this->assertTrue($vaidator->isValid($invalidTextInvalidUrl));
- }
-}
diff --git a/tests/Validator/NullableTest.php b/tests/Validator/NullableTest.php
deleted file mode 100755
index ebd24834..00000000
--- a/tests/Validator/NullableTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-assertTrue($validator->isValid('text'));
- $this->assertTrue($validator->isValid(null));
- $this->assertFalse($validator->isValid(123));
- }
-
- public function testCanReturnValidator(): void
- {
- $validator = new Nullable(new Text(0));
- $this->assertTrue($validator->getValidator() instanceof Text);
- }
-}
diff --git a/tests/Validator/NumericTest.php b/tests/Validator/NumericTest.php
deleted file mode 100755
index 88ea5030..00000000
--- a/tests/Validator/NumericTest.php
+++ /dev/null
@@ -1,24 +0,0 @@
-assertTrue($numeric->isValid('42'));
- $this->assertTrue($numeric->isValid(1337));
- $this->assertTrue($numeric->isValid(0x539));
- $this->assertTrue($numeric->isValid(02471));
- $this->assertTrue($numeric->isValid(1337e0));
- $this->assertTrue($numeric->isValid(9.1));
- $this->assertFalse($numeric->isValid('not numeric'));
- $this->assertFalse($numeric->isValid([]));
- $this->assertFalse($numeric->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_MIXED, $numeric->getType());
- }
-}
diff --git a/tests/Validator/RangeTest.php b/tests/Validator/RangeTest.php
deleted file mode 100755
index aafbab4a..00000000
--- a/tests/Validator/RangeTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
-assertTrue($range->isValid(0));
- $this->assertTrue($range->isValid(1));
- $this->assertTrue($range->isValid(4));
- $this->assertTrue($range->isValid(5));
- $this->assertTrue($range->isValid('5'));
- $this->assertFalse($range->isValid('1.5'));
- $this->assertFalse($range->isValid(6));
- $this->assertFalse($range->isValid(-1));
- $this->assertEquals(0, $range->getMin());
- $this->assertEquals(5, $range->getMax());
- $this->assertFalse($range->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $range->getFormat());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $range->getType());
- }
-
- public function testCanValidateFloatRange()
- {
- $range = new Range(0, 1, \Utopia\Http\Validator::TYPE_FLOAT);
-
- $this->assertTrue($range->isValid(0.0));
- $this->assertTrue($range->isValid(1.0));
- $this->assertTrue($range->isValid(0.5));
- $this->assertTrue($range->isValid('0.5'));
- $this->assertTrue($range->isValid('0.6'));
- $this->assertFalse($range->isValid(4));
- $this->assertFalse($range->isValid(1.5));
- $this->assertFalse($range->isValid(-1));
- $this->assertEquals(0, $range->getMin());
- $this->assertEquals(1, $range->getMax());
- $this->assertFalse($range->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $range->getFormat());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $range->getType(), \Utopia\Http\Validator::TYPE_FLOAT);
- }
-
- public function canValidateInfinityRange()
- {
- $integer = new Range(5, INF, \Utopia\Http\Validator::TYPE_INTEGER);
- $float = new Range(-INF, 45.6, \Utopia\Http\Validator::TYPE_FLOAT);
-
- $this->assertTrue($integer->isValid(25));
- $this->assertFalse($integer->isValid(3));
- $this->assertTrue($integer->isValid(INF));
- $this->assertTrue($float->isValid(32.1));
- $this->assertFalse($float->isValid(97.6));
- $this->assertTrue($float->isValid(-INF));
- }
-}
diff --git a/tests/Validator/TextTest.php b/tests/Validator/TextTest.php
deleted file mode 100755
index 476cfd8a..00000000
--- a/tests/Validator/TextTest.php
+++ /dev/null
@@ -1,88 +0,0 @@
-assertTrue($validator->isValid('text'));
- $this->assertTrue($validator->isValid('7'));
- $this->assertTrue($validator->isValid('7.9'));
- $this->assertTrue($validator->isValid('["seven"]'));
- $this->assertFalse($validator->isValid(['seven']));
- $this->assertFalse($validator->isValid(['seven', 8, 9.0]));
- $this->assertFalse($validator->isValid(false));
- $this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $validator->getType());
- }
-
- public function testCanValidateBoundaries(): void
- {
- $validator = new Text(5);
- $this->assertTrue($validator->isValid('hell'));
- $this->assertTrue($validator->isValid('hello'));
- $this->assertFalse($validator->isValid('hellow'));
- $this->assertFalse($validator->isValid(''));
-
- $validator = new Text(5, 3);
- $this->assertTrue($validator->isValid('hel'));
- $this->assertTrue($validator->isValid('hell'));
- $this->assertTrue($validator->isValid('hello'));
- $this->assertFalse($validator->isValid('hellow'));
- $this->assertFalse($validator->isValid('he'));
- $this->assertFalse($validator->isValid('h'));
- }
-
- public function testCanValidateTextWithAllowList(): void
- {
- // Test lowercase alphabet
- $validator = new Text(100, allowList: Text::ALPHABET_LOWER);
- $this->assertFalse($validator->isArray());
- $this->assertTrue($validator->isValid('qwertzuiopasdfghjklyxcvbnm'));
- $this->assertTrue($validator->isValid('hello'));
- $this->assertTrue($validator->isValid('world'));
- $this->assertFalse($validator->isValid('hello world'));
- $this->assertFalse($validator->isValid('Hello'));
- $this->assertFalse($validator->isValid('worlD'));
- $this->assertFalse($validator->isValid('hello123'));
-
- // Test uppercase alphabet
- $validator = new Text(100, allowList: Text::ALPHABET_UPPER);
- $this->assertFalse($validator->isArray());
- $this->assertTrue($validator->isValid('QWERTZUIOPASDFGHJKLYXCVBNM'));
- $this->assertTrue($validator->isValid('HELLO'));
- $this->assertTrue($validator->isValid('WORLD'));
- $this->assertFalse($validator->isValid('HELLO WORLD'));
- $this->assertFalse($validator->isValid('hELLO'));
- $this->assertFalse($validator->isValid('WORLd'));
- $this->assertFalse($validator->isValid('HELLO123'));
-
- // Test numbers
- $validator = new Text(100, allowList: Text::NUMBERS);
- $this->assertFalse($validator->isArray());
- $this->assertTrue($validator->isValid('1234567890'));
- $this->assertTrue($validator->isValid('123'));
- $this->assertFalse($validator->isValid('123 456'));
- $this->assertFalse($validator->isValid('hello123'));
-
- // Test combination of allowLists
- $validator = new Text(100, allowList: [
- ...Text::ALPHABET_LOWER,
- ...Text::ALPHABET_UPPER,
- ...Text::NUMBERS,
- ]);
-
- $this->assertFalse($validator->isArray());
- $this->assertTrue($validator->isValid('1234567890'));
- $this->assertTrue($validator->isValid('qwertzuiopasdfghjklyxcvbnm'));
- $this->assertTrue($validator->isValid('QWERTZUIOPASDFGHJKLYXCVBNM'));
- $this->assertTrue($validator->isValid('QWERTZUIOPASDFGHJKLYXCVBNMqwertzuiopasdfghjklyxcvbnm1234567890'));
- $this->assertFalse($validator->isValid('hello-world'));
- $this->assertFalse($validator->isValid('hello_world'));
- $this->assertFalse($validator->isValid('hello/world'));
- }
-}
diff --git a/tests/Validator/URLTest.php b/tests/Validator/URLTest.php
deleted file mode 100644
index de530cd1..00000000
--- a/tests/Validator/URLTest.php
+++ /dev/null
@@ -1,43 +0,0 @@
-url = new URL();
- }
-
- public function tearDown(): void
- {
- $this->url = null;
- }
-
- public function testIsValid(): void
- {
- $this->assertEquals('Value must be a valid URL', $this->url->getDescription());
- $this->assertEquals(true, $this->url->isValid('http://example.com'));
- $this->assertEquals(true, $this->url->isValid('https://example.com'));
- $this->assertEquals(true, $this->url->isValid('htts://example.com')); // does not validate protocol
- $this->assertEquals(false, $this->url->isValid('example.com')); // though, requires some kind of protocol
- $this->assertEquals(false, $this->url->isValid('http:/example.com'));
- $this->assertEquals(true, $this->url->isValid('http://exa-mple.com'));
- $this->assertEquals(false, $this->url->isValid('htt@s://example.com'));
- $this->assertEquals(true, $this->url->isValid('http://www.example.com/foo%2\u00c2\u00a9zbar'));
- $this->assertEquals(true, $this->url->isValid('http://www.example.com/?q=%3Casdf%3E'));
- }
-
- public function testIsValidAllowedSchemes(): void
- {
- $this->url = new URL(['http', 'https']);
- $this->assertEquals('Value must be a valid URL with following schemes (http, https)', $this->url->getDescription());
- $this->assertEquals(true, $this->url->isValid('http://example.com'));
- $this->assertEquals(true, $this->url->isValid('https://example.com'));
- $this->assertEquals(false, $this->url->isValid('gopher://www.example.com'));
- }
-}
diff --git a/tests/Validator/WhiteListTest.php b/tests/Validator/WhiteListTest.php
deleted file mode 100755
index f024908c..00000000
--- a/tests/Validator/WhiteListTest.php
+++ /dev/null
@@ -1,58 +0,0 @@
-assertTrue($whiteList->isValid(3));
- $this->assertTrue($whiteList->isValid(4));
- $this->assertTrue($whiteList->isValid('string1'));
- $this->assertTrue($whiteList->isValid('string2'));
-
- $this->assertFalse($whiteList->isValid('string3'));
- $this->assertFalse($whiteList->isValid('STRING1'));
- $this->assertFalse($whiteList->isValid('strIng1'));
- $this->assertFalse($whiteList->isValid('3'));
- $this->assertFalse($whiteList->isValid(5));
- $this->assertFalse($whiteList->isArray());
- $this->assertEquals($whiteList->getList(), ['string1', 'string2', 3, 4]);
- $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $whiteList->getType());
- }
-
- public function testCanValidateLoosely(): void
- {
- $whiteList = new WhiteList(['string1', 'string2', 3, 4]);
-
- $this->assertTrue($whiteList->isValid(3));
- $this->assertTrue($whiteList->isValid(4));
- $this->assertTrue($whiteList->isValid('string1'));
- $this->assertTrue($whiteList->isValid('string2'));
- $this->assertTrue($whiteList->isValid('STRING1'));
- $this->assertTrue($whiteList->isValid('strIng1'));
- $this->assertTrue($whiteList->isValid('3'));
- $this->assertTrue($whiteList->isValid('4'));
- $this->assertFalse($whiteList->isValid('string3'));
- $this->assertFalse($whiteList->isValid(5));
- $this->assertEquals($whiteList->getList(), ['string1', 'string2', 3, 4]);
-
- $whiteList = new WhiteList(['STRING1', 'STRING2', 3, 4]);
-
- $this->assertTrue($whiteList->isValid(3));
- $this->assertTrue($whiteList->isValid(4));
- $this->assertTrue($whiteList->isValid('string1'));
- $this->assertTrue($whiteList->isValid('string2'));
- $this->assertTrue($whiteList->isValid('STRING1'));
- $this->assertTrue($whiteList->isValid('strIng1'));
- $this->assertTrue($whiteList->isValid('3'));
- $this->assertTrue($whiteList->isValid('4'));
- $this->assertFalse($whiteList->isValid('string3'));
- $this->assertFalse($whiteList->isValid(5));
- $this->assertEquals($whiteList->getList(), ['string1', 'string2', 3, 4]);
- }
-}
diff --git a/tests/Validator/WildcardTest.php b/tests/Validator/WildcardTest.php
deleted file mode 100644
index 4de71475..00000000
--- a/tests/Validator/WildcardTest.php
+++ /dev/null
@@ -1,21 +0,0 @@
-assertTrue($validator->isValid([0 => 'string', 1 => 'string']));
- $this->assertTrue($validator->isValid(''));
- $this->assertTrue($validator->isValid([]));
- $this->assertTrue($validator->isValid(1));
- $this->assertTrue($validator->isValid(true));
- $this->assertTrue($validator->isValid(false));
- $this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $validator->getType());
- }
-}
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/init.php b/tests/e2e/init.php
index 54638fd9..fb0d9d59 100644
--- a/tests/e2e/init.php
+++ b/tests/e2e/init.php
@@ -5,7 +5,7 @@
use Utopia\Http\Http;
use Utopia\Http\Request;
use Utopia\Http\Response;
-use Utopia\Http\Validator\Text;
+use Utopia\Validator\Text;
ini_set('memory_limit', '1024M');
ini_set('display_errors', '1');