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');