diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
index c034a667..0b6ca990 100644
--- a/.github/ISSUE_TEMPLATE/bug.yaml
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -34,7 +34,7 @@ body:
- type: dropdown
id: utopia-version
attributes:
- label: "🎲 Utopia Framework version"
+ label: "🎲 Utopia HTTP version"
description: "What version of Utopia are you running?"
options:
- Version 0.18.x
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cc41aa6f..087875d5 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,13 +13,19 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
- php-version: '8.0'
+ php-version: '8.2'
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
- name: Setup Docker
- run: docker-compose up -d --build
+ run: docker compose up -d --build
- name: Wait for Server to be ready
run: sleep 10
- - name: Run Tests
- run: docker compose exec web vendor/bin/phpunit --configuration phpunit.xml
\ No newline at end of file
+ - name: Run FPM Tests
+ run: docker compose exec fpm vendor/bin/phpunit --configuration phpunit.xml
+
+ - name: Run Swoole Tests
+ run: docker compose exec swoole vendor/bin/phpunit --configuration phpunit.xml
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7cc480d1..361c6a7b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,16 +1,16 @@
# Contributing
-We would ❤️ for you to contribute to Framework and help make it better! We want contributing to Framework to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, new docs as well as updates and tweaks, blog posts, workshops, and more.
+We would ❤️ for you to contribute to Utopia HTTP and help make it better! We want contributing to this library to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, new docs as well as updates and tweaks, blog posts, workshops, and more.
## How to Start?
If you are worried or don’t know where to start, check out our next section explaining what kind of help we could use and where can you get involved. You can reach out with questions to [Eldad Fux (@eldadfux)](https://twitter.com/eldadfux) or [@appwrite_io](https://twitter.com/appwrite_io) on Twitter, and anyone from the [Appwrite team on Discord](https://discord.gg/GSeTUeA). You can also submit an issue, and a maintainer can guide you!
-You can get an in-depth understanding of the Utopia-PHP framework in our [Getting Started](docs/Getting-Starting-Guide.md) guide.
+You can get an in-depth understanding of Utopia HTTP in our [Getting Started](docs/Getting-Starting-Guide.md) guide.
## Code of Conduct
-Help us keep framework open and inclusive. Please read and follow our [Code of Conduct](/CODE_OF_CONDUCT.md).
+Help us keep Utopia HTTP open and inclusive. Please read and follow our [Code of Conduct](/CODE_OF_CONDUCT.md).
## Submit a Pull Request 🚀
@@ -64,35 +64,41 @@ $ git push origin [name_of_your_new_branch]
8. After approval, merge your PR
9. GitHub will automatically delete the branch after the merge is done. (they can still be restored).
+### Testing
+
+- `docker compose up -d`
+- `docker compose exec web vendor/bin/phpunit --configuration phpunit.xml`
+- `docker compose exec web vendor/bin/psalm --show-info=true`
+
## Introducing New Features
-We would 💖 you to contribute to Framework, but we would also like to make sure Framework is as great as possible and loyal to its vision and mission statement 🙏.
+We would 💖 you to contribute to Utopia HTTP, but we would also like to make sure this library is as great as possible and loyal to its vision and mission statement 🙏.
For us to find the right balance, please open an issue explaining your ideas before introducing a new pull request.
-This will allow the Framework community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision.
+This will allow the community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision.
-This is also important for the Framework lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc).
+This is also important for the repository owners to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc).
## Other Ways to Help
-Pull requests are great, but there are many other areas where you can help Framework
+Pull requests are great, but there are many other areas where you can help:
### Blogging & Speaking
-Blogging, speaking about, or creating tutorials about one of Framework's many features is great way to contribute and help our project grow.
+Creating blog posts, giving talks, or developing tutorials about one of this library's many features are excellent ways to contribute and help our project grow.
### Presenting at Meetups
-Presenting at meetups and conferences about your Framework projects. Your unique challenges and successes in building things with Framework can provide great speaking material. We’d love to review your talk abstract/CFP, so get in touch with us if you’d like some help!
+Presenting at meetups and conferences about your Utopia projects. Your unique challenges and successes in building things with this library can provide great speaking material. We’d love to review your conference talk abstract, so get in touch with us if you’d like some help!
### Sending Feedbacks & Reporting Bugs
-Sending feedback is a great way for us to understand your different use cases of Framework better. If you had any issues, bugs, or want to share about your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA).
+Sending feedback is a great way for us to understand your different use cases of this library better. If you had any issues, bugs, or want to share about your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA).
### Submitting New Ideas
-If you think Framework could use a new feature, please open an issue on our GitHub repository, stating as much information as you can think about your new idea and it's implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature.
+If you think this library could use a new feature, please open an issue on our GitHub repository, stating as much information as you can think about your new idea and it's implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature.
### Improving Documentation
@@ -100,4 +106,4 @@ Submitting documentation updates, enhancements, designs, or bug fixes. Spelling
### Helping Someone
-Searching for Framework on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Framework's repo!
+Searching for Utopia HTTP on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to this repo!
diff --git a/Dockerfile b/Dockerfile.fpm
similarity index 84%
rename from Dockerfile
rename to Dockerfile.fpm
index 54c948a6..4b17ec96 100644
--- a/Dockerfile
+++ 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
new file mode 100644
index 00000000..ff80ec9f
--- /dev/null
+++ b/Dockerfile.swoole
@@ -0,0 +1,29 @@
+FROM composer:2.0 AS step0
+
+
+ARG TESTING=true
+
+ENV TESTING=$TESTING
+
+WORKDIR /usr/local/src/
+
+COPY composer.* /usr/local/src/
+
+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.5.0 AS final
+LABEL maintainer="team@appwrite.io"
+
+WORKDIR /usr/src/code
+
+COPY ./src /usr/src/code/src
+COPY ./tests /usr/src/code/tests
+COPY ./phpunit.xml /usr/src/code/phpunit.xml
+COPY ./phpbench.json /usr/src/code/phpbench.json
+COPY --from=step0 /usr/local/src/vendor /usr/src/code/vendor
+
+EXPOSE 80
+
+CMD ["php", "tests/e2e/server-swoole.php"]
diff --git a/README.md b/README.md
index ac3fc084..f55045f4 100644
--- a/README.md
+++ b/README.md
@@ -2,34 +2,40 @@
-[](https://travis-ci.org/utopia-php/framework)
-
+[](https://travis-ci.org/utopia-php/http)
+
[](https://discord.gg/GSeTUeA)
-Utopia Framework 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 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 Framework is dependency free. Any extra features such as authentication, caching will be available as standalone models in order to keep the framework core as 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
-Install using composer:
+Install using Composer:
+
```bash
-composer require utopia-php/framework
+composer require utopia-php/http
```
-Init your first application:
+Init your first application in `src/server.php`:
+
```php
-require_once __DIR__ . '/../../vendor/autoload.php';
+require_once __DIR__.'/../vendor/autoload.php';
-use Utopia\App;
-use Utopia\Request;
-use Utopia\Response;
+use Utopia\DI\Container;
+use Utopia\Http\Http;
+use Utopia\Http\Request;
+use Utopia\Http\Response;
+use Utopia\Http\Adapter\FPM\Server;
-App::get('/hello-world') // Define Route
+$container = new Container();
+
+Http::get('/hello-world') // Define Route
->inject('request')
->inject('response')
->action(
- function($request, $response) {
+ function(Request $request, Response $response) {
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
@@ -38,90 +44,230 @@ App::get('/hello-world') // Define Route
}
);
-App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
+Http::setMode(Http::MODE_TYPE_PRODUCTION);
-$app = new App('America/New_York');
-$request = new Request();
-$response = new Response();
+$http = new Http(new Server(), 'America/New_York', $container);
+$http->start();
+```
-$app->run($request, $response);
+Run HTTP server:
+
+```bash
+php -S localhost:8000 src/server.php
```
-### Hooks
+Send HTTP request:
+
+```bash
+curl http://localhost:8000/hello-world
+```
+
+### Server Adapters
+
+The library supports server adapters to be able to run on any PHP setup. You could use the FPM or Swoole server.
+
+#### Use PHP FPM server
-There are three types of hooks, init hooks, shutdown hooks and error hooks. Init hooks are executed before the route action is executed. Shutdown hook is executed after route action is executed before application shuts down. Finally error hooks are executed whenever there's an error in the application lifecycle. You can provide multiple hooks for each stage. If you do not assign groups to the hook, by default the hook will be executed for every route.
+```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(
+ function(Response $response) {
+ $response->send('Hello from PHP FPM');
+ }
+ );
+
+$http = new Http(new Server(), 'America/New_York', $container);
+$http->start();
+```
+
+> When using PHP FPM, you can use the command `php -S localhost:80 src/server.php` to run the HTTP server locally
+
+#### Using Swoole server
```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\Swoole\Server;
-use Utopia\App;
-use Utopia\Request;
-use Utopia\Response;
+$container = new Container();
-App::init()
+Http::get('/')
+ ->inject('request')
->inject('response')
- ->action(function($response) {
- $response->addHeader('content-type', 'application/json');
+ ->action(
+ function(Request $request, Response $response) {
+ $response->send('Hello from Swoole');
+ }
+ );
+
+$http = new Http(new Server('0.0.0.0', '80'), 'America/New_York', $container);
+$http->start();
+```
+
+> When using Swoole, you can use the command `php src/server.php` to run the HTTP server locally, but you need Swoole installed. For setup with Docker, check out our [example application](/example)
+
+### Parameters
+
+Parameters are used to receive input into endpoint action from the HTTP request. Parameters could be defined as URL parameters or in a body with a structure such as JSON.
+
+Every parameter must have a validator defined. Validators are simple classes that verify the input and ensure the security of inputs. You can define your own validators or use some of [built-in validators](/src/Http/Validator).
+
+Define an endpoint with params:
+
+```php
+Http::get('/')
+ ->param('name', 'World', new Text(256), 'Name to greet. Optional, max length 256.', true)
+ ->inject('response')
+ ->action(function(string $name, Response $response) {
+ $response->send('Hello ' . $name);
});
+```
-App::error()
+Send HTTP requests to ensure the parameter works:
+
+```bash
+curl http://localhost:8000/hello-world
+curl http://localhost:8000/hello-world?name=Utopia
+curl http://localhost:8000/hello-world?name=Appwrite
+```
+
+It's always recommended to use params instead of getting params or body directly from the request resource. If you do that intentionally, always make sure to run validation right after fetching such a raw input.
+
+### Hooks
+
+There are three types of hooks:
+
+- **Init hooks** are executed before the route action is executed
+- **Shutdown hooks** are executed after route action is finished, but before application shuts down
+- **Error hooks** are executed whenever there's an error in the application lifecycle.
+
+You can provide multiple hooks for each stage. If you do not assign groups to the hook, by default, the hook will be executed for every route. If a group is defined on a hook, it will only run during the lifecycle of a request with the same group name on the action.
+
+```php
+Http::init()
+ ->inject('request')
+ ->action(function(Request $request) {
+ \var_dump("Recieved: " . $request->getMethod() . ' ' . $request->getURI());
+ });
+
+Http::shutdown()
+ ->inject('response')
+ ->action(function(Response $response) {
+ \var_dump('Responding with status code: ' . $response->getStatusCode());
+ });
+
+Http::error()
->inject('error')
->inject('response')
- ->action(function($error, $response) {
+ ->action(function(\Throwable $error, Response $response) {
$response
->setStatusCode(500)
->send('Error occurred ' . $error);
});
+```
-App::get('/hello-world') // Define Route
- ->inject('request')
+Hooks are designed to be actions that run during the lifecycle of requests. Hooks should include functional logic. Hooks are not designed to prepare dependencies or context for the request. For such a use case, you should use resources.
+
+### Groups
+
+Groups allow you to define common behavior for multiple endpoints.
+
+You can start by defining a group on an endpoint. Keep in mind you can also define multiple groups on a single endpoint.
+
+```php
+Http::get('/v1/health')
+ ->groups(['api', 'public'])
->inject('response')
->action(
- function($request, $response) {
- $response
- ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
- ->addHeader('Expires', '0')
- ->addHeader('Pragma', 'no-cache')
- ->json(['Hello' => 'World']);
+ function(Response $response) {
+ $response->send('OK');
}
);
+```
+
+Now you can define hooks that would apply only to specific groups. Remember, hooks can also be assigned to multiple groups.
+
+```php
+Http::init()
+ ->groups(['api'])
+ ->inject('request')
+ ->inject('response')
+ ->action(function(Request $request, Response $response) {
+ $apiKey = $request->getHeader('x-api-key', '');
+
+ if(empty($apiKey)) {
+ $response
+ ->setStatusCode(Response::STATUS_CODE_UNAUTHORIZED)
+ ->send('API key missing.');
+ }
+ });
+```
+
+Groups are designed to be actions that run during the lifecycle of requests to endpoints that have some logic in common. Groups allow you to prevent code duplication and are designed to be defined anywhere in your source code to allow flexibility.
-App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
+### Resources
-$app = new App('America/New_York');
-$request = new Request();
-$response = new Response();
+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.
-$app->run($request, $response);
+Define a dependency on the DI container:
+
+```php
+$container->set('bootTime', function () {
+ return \microtime(true);
+});
+```
+
+Inject resource into endpoint action:
+
+```php
+$http = new Http(new Server(), 'America/New_York', $container);
+
+Http::get('/')
+ ->inject('bootTime')
+ ->inject('response')
+ ->action(function(float $bootTime, Response $response) {
+ $response->send('Process started at: ' . \strval($bootTime));
+ });
```
+Inject resource into a hook:
+
+```php
+Http::shutdown()
+ ->inject('bootTime')
+ ->action(function(float $bootTime) {
+ $uptime = \microtime(true) - $bootTime;
+ \var_dump("Process uptime: " . $uptime . " seconds");
+ });
+```
+
+In advanced scenarios, resources can also be injected into other resources or endpoint parameters.
+
+Resources are designed to prepare dependencies or context for the request. Resources are not meant to do functional logic or return callbacks. For such a use case, you should use hooks.
+
+To learn more about architecture and features for this library, check out more in-depth [Getting started guide](/docs/Getting-Starting-Guide.md).
+
## System Requirements
-Utopia Framework requires PHP 8.0 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
-Our ecosystem support other thin PHP projects aiming to extend the core PHP Utopia framework.
-
-Each project is focused on solving a single, very simple problem and you can use composer to include any of them in your next project.
-
-Library | Description
---- | ---
-**[Utopia AB](https://github.com/utopia-php/ab)** | Simple PHP library for managing AB testing on the server side.
-**[Utopia Abuse](https://github.com/utopia-php/abuse)** | Simple PHP library for rate limiting usage of different features in your app or API.
-**[Utopia Analytics](https://github.com/utopia-php/analytics)** | Simple PHP library to send information about events or pageviews to Google Analytics.
-**[Utopia Audit](https://github.com/utopia-php/audit)** | Simple PHP library for audit logging users actions and system events
-**[Utopia Cache](https://github.com/utopia-php/cache)** | Simple PHP library for managing cache with different storage adapters.
-**[Utopia CLI](https://github.com/utopia-php/cli)** | Simple PHP library for for building simple command line tools.
-**[Utopia Config](https://github.com/utopia-php/config)** | Simple PHP library for managing your app configuration.
-**[Utopia Database](https://github.com/utopia-php/database)** | Simple PHP library for managing application persistency. It supports multiple database adapters.
-**[Utopia Domains](https://github.com/utopia-php/domains)** | Simple PHP library for parsing domain names.
-**[Utopia Image](https://github.com/utopia-php/image)** | Simple PHP library for creating common image manipulations that is easy to use.
-**[Utopia Locale](https://github.com/utopia-php/locale)** | Simple PHP library for adding support to multiple locales in your app or API.
-**[Utopia Preloader](https://github.com/utopia-php/preloader)** | Simple PHP library for managing PHP preloading configuration.
-**[Utopia Registry](https://github.com/utopia-php/registry)** | Simple PHP library for dependency injection and lazy loading of objects or resources.
-**[Utopia System](https://github.com/utopia-php/system)** | Simple PHP library for obtaining information about the host's system.
-**[Utopia Storage](https://github.com/utopia-php/storage)** | Simple and lite PHP library for managing application storage. It supports multiple storage adapters.
+Our ecosystem supports other thin PHP projects aiming to extend the core PHP Utopia HTTP.
+
+Each project is focused on solving a single, very simple problem and you can use composer to include any of them in your next project.
+
+You can find all libraries in [GitHub Utopia organization](https://github.com/utopia-php).
## Contributing
@@ -129,16 +275,10 @@ All code contributions - including those of people having commit access - must g
Fork the project, create a feature branch, and send us a pull request.
-You can refer to the [Contributing Guide](https://github.com/utopia-php/framework/blob/master/CONTRIBUTING.md) for more info.
+You can refer to the [Contributing Guide](https://github.com/utopia-php/http/blob/master/CONTRIBUTING.md) for more info.
For security issues, please email security@appwrite.io instead of posting a public issue in GitHub.
-### Testing
-
- - `docker-compose up -d`
- - `docker-compose exec web vendor/bin/phpunit --configuration phpunit.xml`
- - `docker-compose exec web vendor/bin/psalm --show-info=true`
-
## Copyright and license
The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php)
diff --git a/composer.json b/composer.json
index fe84d0d0..40e0392d 100644
--- a/composer.json
+++ b/composer.json
@@ -1,33 +1,51 @@
{
- "name": "utopia-php/framework",
- "description": "A simple, light and advanced PHP framework",
+ "name": "utopia-php/http",
+ "description": "A simple, light and advanced PHP HTTP framework",
"type": "library",
"keywords": [
"php",
"framework",
+ "http",
"upf"
],
"license": "MIT",
"minimum-stability": "stable",
"autoload": {
"psr-4": {
- "Utopia\\": "src/"
+ "Utopia\\": "src/",
+ "Tests\\E2E\\": "tests/e2e"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Utopia\\Http\\Tests\\": "tests/"
}
},
"scripts": {
"lint": "vendor/bin/pint --test",
"format": "vendor/bin/pint",
- "check": "vendor/bin/phpstan analyse -c phpstan.neon",
+ "check": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 512M",
"test": "vendor/bin/phpunit --configuration phpunit.xml",
"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": "*"
+ },
+ "config": {
+ "allow-plugins": {
+ "php-http/discovery": true,
+ "tbachert/spi": true
+ }
},
"require-dev": {
+ "doctrine/instantiator": "^1.5",
+ "laravel/pint": "1.*",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/phpstan": "1.*",
"phpunit/phpunit": "^9.5.25",
- "laravel/pint": "^1.2",
- "phpstan/phpstan": "^1.10",
- "phpbench/phpbench": "^1.2"
+ "swoole/ide-helper": "4.8.3"
}
}
diff --git a/composer.lock b/composer.lock
index 9bcef239..c854c2ab 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,21 +4,171 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d41ea47cadd897ad4053410229874733",
- "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",
- "version": "2.0.1",
+ "version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/annotations.git",
- "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f"
+ "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f",
- "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f",
+ "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7",
+ "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7",
"shasum": ""
},
"require": {
@@ -30,10 +180,10 @@
"require-dev": {
"doctrine/cache": "^2.0",
"doctrine/coding-standard": "^10",
- "phpstan/phpstan": "^1.8.0",
+ "phpstan/phpstan": "^1.10.28",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
- "symfony/cache": "^5.4 || ^6",
- "vimeo/psalm": "^4.10"
+ "symfony/cache": "^5.4 || ^6.4 || ^7",
+ "vimeo/psalm": "^4.30 || ^5.14"
},
"suggest": {
"php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations"
@@ -79,36 +229,37 @@
],
"support": {
"issues": "https://github.com/doctrine/annotations/issues",
- "source": "https://github.com/doctrine/annotations/tree/2.0.1"
+ "source": "https://github.com/doctrine/annotations/tree/2.0.2"
},
- "time": "2023-02-02T22:02:53+00:00"
+ "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,31 +302,31 @@
"type": "tidelift"
}
],
- "time": "2022-12-30T00:23:10+00:00"
+ "time": "2022-12-30T00:15:36+00:00"
},
{
"name": "doctrine/lexer",
- "version": "3.0.0",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
- "reference": "84a527db05647743d50373e0ec53a152f2cde568"
+ "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568",
- "reference": "84a527db05647743d50373e0ec53a152f2cde568",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
+ "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
- "doctrine/coding-standard": "^10",
- "phpstan/phpstan": "^1.9",
- "phpunit/phpunit": "^9.5",
+ "doctrine/coding-standard": "^12",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^10.5",
"psalm/plugin-phpunit": "^0.18.3",
- "vimeo/psalm": "^5.0"
+ "vimeo/psalm": "^5.21"
},
"type": "library",
"autoload": {
@@ -212,7 +363,7 @@
],
"support": {
"issues": "https://github.com/doctrine/lexer/issues",
- "source": "https://github.com/doctrine/lexer/tree/3.0.0"
+ "source": "https://github.com/doctrine/lexer/tree/3.0.1"
},
"funding": [
{
@@ -228,20 +379,20 @@
"type": "tidelift"
}
],
- "time": "2022-12-15T16:57:16+00:00"
+ "time": "2024-02-05T11:56:58+00:00"
},
{
"name": "laravel/pint",
- "version": "v1.13.7",
+ "version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece"
+ "reference": "bdec963f53172c5e36330f3a400604c69bf02d39"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/4157768980dbd977f1c4b4cc94997416d8b30ece",
- "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39",
+ "reference": "bdec963f53172c5e36330f3a400604c69bf02d39",
"shasum": ""
},
"require": {
@@ -249,16 +400,17 @@
"ext-mbstring": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
- "php": "^8.1.0"
+ "php": "^8.2.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.38.0",
- "illuminate/view": "^10.30.1",
- "laravel-zero/framework": "^10.3.0",
- "mockery/mockery": "^1.6.6",
- "nunomaduro/larastan": "^2.6.4",
- "nunomaduro/termwind": "^1.15.1",
- "pestphp/pest": "^2.24.2"
+ "friendsofphp/php-cs-fixer": "^3.94.2",
+ "illuminate/view": "^12.54.1",
+ "larastan/larastan": "^3.9.3",
+ "laravel-zero/framework": "^12.0.5",
+ "mockery/mockery": "^1.6.12",
+ "nunomaduro/termwind": "^2.4.0",
+ "pestphp/pest": "^3.8.6",
+ "shipfastlabs/agent-detector": "^1.1.0"
},
"bin": [
"builds/pint"
@@ -284,6 +436,7 @@
"description": "An opinionated code formatter for PHP.",
"homepage": "https://laravel.com",
"keywords": [
+ "dev",
"format",
"formatter",
"lint",
@@ -294,20 +447,20 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2023-12-05T19:43:12+00:00"
+ "time": "2026-03-12T15:51:39+00:00"
},
{
"name": "myclabs/deep-copy",
- "version": "1.11.1",
+ "version": "1.13.4",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
"require": {
@@ -315,11 +468,12 @@
},
"conflict": {
"doctrine/collections": "<1.6.8",
- "doctrine/common": "<2.13.3 || >=3,<3.2.2"
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
@@ -345,7 +499,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
},
"funding": [
{
@@ -353,29 +507,31 @@
"type": "tidelift"
}
],
- "time": "2023-03-08T13:26:56+00:00"
+ "time": "2025-08-01T08:46:24+00:00"
},
{
"name": "nikic/php-parser",
- "version": "v4.18.0",
+ "version": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
- "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"shasum": ""
},
"require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
"ext-tokenizer": "*",
- "php": ">=7.0"
+ "php": ">=7.4"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
@@ -383,7 +539,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.9-dev"
+ "dev-master": "5.x-dev"
}
},
"autoload": {
@@ -407,26 +563,27 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
},
- "time": "2023-12-10T21:03:43+00:00"
+ "time": "2025-12-06T11:56:16+00:00"
},
{
"name": "phar-io/manifest",
- "version": "2.0.3",
+ "version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
- "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
- "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
"shasum": ""
},
"require": {
"ext-dom": "*",
+ "ext-libxml": "*",
"ext-phar": "*",
"ext-xmlwriter": "*",
"phar-io/version": "^3.0.1",
@@ -467,9 +624,15 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
- "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
},
- "time": "2021-07-20T11:28:43+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
},
{
"name": "phar-io/version",
@@ -524,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"
},
@@ -569,73 +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"
- },
- {
- "name": "phpbench/dom",
- "version": "0.3.3",
- "source": {
- "type": "git",
- "url": "https://github.com/phpbench/dom.git",
- "reference": "786a96db538d0def931f5b19225233ec42ec7a72"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpbench/dom/zipball/786a96db538d0def931f5b19225233ec42ec7a72",
- "reference": "786a96db538d0def931f5b19225233ec42ec7a72",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "php": "^7.3||^8.0"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "^3.14",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^8.0||^9.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "PhpBench\\Dom\\": "lib/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Daniel Leech",
- "email": "daniel@dantleech.com"
- }
- ],
- "description": "DOM wrapper to simplify working with the PHP DOM implementation",
- "support": {
- "issues": "https://github.com/phpbench/dom/issues",
- "source": "https://github.com/phpbench/dom/tree/0.3.3"
- },
- "time": "2023-03-06T23:46:57+00:00"
+ "time": "2025-11-06T09:05:13+00:00"
},
{
"name": "phpbench/phpbench",
- "version": "1.2.15",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpbench/phpbench.git",
- "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c"
+ "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpbench/phpbench/zipball/f7000319695cfad04a57fc64bf7ef7abdf4c437c",
- "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c",
+ "url": "https://api.github.com/repos/phpbench/phpbench/zipball/9a28fd0833f11171b949843c6fd663eb69b6d14c",
+ "reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c",
"shasum": ""
},
"require": {
@@ -646,30 +758,31 @@
"ext-reflection": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
- "php": "^8.1",
- "phpbench/container": "^2.1",
- "phpbench/dom": "~0.3.3",
+ "php": "^8.2",
+ "phpbench/container": "^2.2",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"seld/jsonlint": "^1.1",
- "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0",
- "symfony/filesystem": "^4.2 || ^5.0 || ^6.0 || ^7.0",
- "symfony/finder": "^4.2 || ^5.0 || ^6.0 || ^7.0",
- "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0",
- "symfony/process": "^4.2 || ^5.0 || ^6.0 || ^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",
- "friendsofphp/php-cs-fixer": "^3.0",
+ "ergebnis/composer-normalize": "^2.39",
"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.0",
- "rector/rector": "^0.18.10",
- "symfony/error-handler": "^5.2 || ^6.0 || ^7.0",
- "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ "phpunit/phpunit": "^11.5",
+ "rector/rector": "^1.2",
+ "sebastian/exporter": "^6.3.2",
+ "symfony/error-handler": "^6.1 || ^7.0 || ^8.0",
+ "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0"
},
"suggest": {
"ext-xdebug": "For Xdebug profiling extension."
@@ -712,7 +825,7 @@
],
"support": {
"issues": "https://github.com/phpbench/phpbench/issues",
- "source": "https://github.com/phpbench/phpbench/tree/1.2.15"
+ "source": "https://github.com/phpbench/phpbench/tree/1.5.1"
},
"funding": [
{
@@ -720,20 +833,15 @@
"type": "github"
}
],
- "time": "2023-11-29T12:21:11+00:00"
+ "time": "2026-03-05T08:18:58+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.10.50",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
- },
+ "version": "1.12.33",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
- "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1",
+ "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1",
"shasum": ""
},
"require": {
@@ -776,45 +884,41 @@
{
"url": "https://github.com/phpstan",
"type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
- "type": "tidelift"
}
],
- "time": "2023-12-13T10:59:42+00:00"
+ "time": "2026-02-28T20:30:03+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.30",
+ "version": "9.2.32",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089"
+ "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089",
- "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5",
+ "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.18 || ^5.0",
+ "nikic/php-parser": "^4.19.1 || ^5.1.0",
"php": ">=7.3",
- "phpunit/php-file-iterator": "^3.0.3",
- "phpunit/php-text-template": "^2.0.2",
- "sebastian/code-unit-reverse-lookup": "^2.0.2",
- "sebastian/complexity": "^2.0",
- "sebastian/environment": "^5.1.2",
- "sebastian/lines-of-code": "^1.0.3",
- "sebastian/version": "^3.0.1",
- "theseer/tokenizer": "^1.2.0"
+ "phpunit/php-file-iterator": "^3.0.6",
+ "phpunit/php-text-template": "^2.0.4",
+ "sebastian/code-unit-reverse-lookup": "^2.0.3",
+ "sebastian/complexity": "^2.0.3",
+ "sebastian/environment": "^5.1.5",
+ "sebastian/lines-of-code": "^1.0.4",
+ "sebastian/version": "^3.0.2",
+ "theseer/tokenizer": "^1.2.3"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^9.6"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
@@ -823,7 +927,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "9.2-dev"
+ "dev-main": "9.2.x-dev"
}
},
"autoload": {
@@ -852,7 +956,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32"
},
"funding": [
{
@@ -860,7 +964,7 @@
"type": "github"
}
],
- "time": "2023-12-22T06:47:57+00:00"
+ "time": "2024-08-22T04:23:01+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -1105,45 +1209,45 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.15",
+ "version": "9.6.34",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1"
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1",
- "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a",
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a",
"shasum": ""
},
"require": {
- "doctrine/instantiator": "^1.3.1 || ^2",
+ "doctrine/instantiator": "^1.5.0 || ^2",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.10.1",
- "phar-io/manifest": "^2.0.3",
- "phar-io/version": "^3.0.2",
+ "myclabs/deep-copy": "^1.13.4",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
"php": ">=7.3",
- "phpunit/php-code-coverage": "^9.2.28",
- "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-code-coverage": "^9.2.32",
+ "phpunit/php-file-iterator": "^3.0.6",
"phpunit/php-invoker": "^3.1.1",
- "phpunit/php-text-template": "^2.0.3",
- "phpunit/php-timer": "^5.0.2",
- "sebastian/cli-parser": "^1.0.1",
- "sebastian/code-unit": "^1.0.6",
- "sebastian/comparator": "^4.0.8",
- "sebastian/diff": "^4.0.3",
- "sebastian/environment": "^5.1.3",
- "sebastian/exporter": "^4.0.5",
- "sebastian/global-state": "^5.0.1",
- "sebastian/object-enumerator": "^4.0.3",
- "sebastian/resource-operations": "^3.0.3",
- "sebastian/type": "^3.2",
+ "phpunit/php-text-template": "^2.0.4",
+ "phpunit/php-timer": "^5.0.3",
+ "sebastian/cli-parser": "^1.0.2",
+ "sebastian/code-unit": "^1.0.8",
+ "sebastian/comparator": "^4.0.10",
+ "sebastian/diff": "^4.0.6",
+ "sebastian/environment": "^5.1.5",
+ "sebastian/exporter": "^4.0.8",
+ "sebastian/global-state": "^5.0.8",
+ "sebastian/object-enumerator": "^4.0.4",
+ "sebastian/resource-operations": "^3.0.4",
+ "sebastian/type": "^3.2.1",
"sebastian/version": "^3.0.2"
},
"suggest": {
@@ -1188,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.15"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34"
},
"funding": [
{
@@ -1199,12 +1303,20 @@
"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/phpunit/phpunit",
"type": "tidelift"
}
],
- "time": "2023-12-01T16:55:19+00:00"
+ "time": "2026-01-27T05:45:00+00:00"
},
{
"name": "psr/cache",
@@ -1255,71 +1367,18 @@
},
"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.0",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
@@ -1354,22 +1413,22 @@
"psr-3"
],
"support": {
- "source": "https://github.com/php-fig/log/tree/3.0.0"
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
},
- "time": "2021-07-14T16:46:02+00:00"
+ "time": "2024-09-11T13:17:53+00:00"
},
{
"name": "sebastian/cli-parser",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
- "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
"shasum": ""
},
"require": {
@@ -1404,7 +1463,7 @@
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
- "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
},
"funding": [
{
@@ -1412,7 +1471,7 @@
"type": "github"
}
],
- "time": "2020-09-28T06:08:49+00:00"
+ "time": "2024-03-02T06:27:43+00:00"
},
{
"name": "sebastian/code-unit",
@@ -1527,16 +1586,16 @@
},
{
"name": "sebastian/comparator",
- "version": "4.0.8",
+ "version": "4.0.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "fa0f136dd2334583309d32b62544682ee972b51a"
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
- "reference": "fa0f136dd2334583309d32b62544682ee972b51a",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d",
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d",
"shasum": ""
},
"require": {
@@ -1589,15 +1648,27 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
- "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10"
},
"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/comparator",
+ "type": "tidelift"
}
],
- "time": "2022-09-14T12:41:17+00:00"
+ "time": "2026-01-24T09:22:56+00:00"
},
{
"name": "sebastian/complexity",
@@ -1658,16 +1729,16 @@
},
{
"name": "sebastian/diff",
- "version": "4.0.5",
+ "version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
- "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
"shasum": ""
},
"require": {
@@ -1712,7 +1783,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
- "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
},
"funding": [
{
@@ -1720,7 +1791,7 @@
"type": "github"
}
],
- "time": "2023-05-07T05:35:17+00:00"
+ "time": "2024-03-02T06:30:58+00:00"
},
{
"name": "sebastian/environment",
@@ -1787,16 +1858,16 @@
},
{
"name": "sebastian/exporter",
- "version": "4.0.5",
+ "version": "4.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
- "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
"shasum": ""
},
"require": {
@@ -1852,28 +1923,40 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
- "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
+ "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": "2022-09-14T06:03:37+00:00"
+ "time": "2025-09-24T06:03:27+00:00"
},
{
"name": "sebastian/global-state",
- "version": "5.0.6",
+ "version": "5.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "bde739e7565280bda77be70044ac1047bc007e34"
+ "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34",
- "reference": "bde739e7565280bda77be70044ac1047bc007e34",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+ "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
"shasum": ""
},
"require": {
@@ -1916,15 +1999,27 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
- "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.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/global-state",
+ "type": "tidelift"
}
],
- "time": "2023-08-02T09:26:13+00:00"
+ "time": "2025-08-10T07:10:35+00:00"
},
{
"name": "sebastian/lines-of-code",
@@ -2097,16 +2192,16 @@
},
{
"name": "sebastian/recursion-context",
- "version": "4.0.5",
+ "version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
+ "reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
- "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
+ "reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
"shasum": ""
},
"require": {
@@ -2148,28 +2243,40 @@
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
- "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
},
"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/recursion-context",
+ "type": "tidelift"
}
],
- "time": "2023-02-03T06:07:39+00:00"
+ "time": "2025-08-10T06:57:39+00:00"
},
{
"name": "sebastian/resource-operations",
- "version": "3.0.3",
+ "version": "3.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
- "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
- "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
"shasum": ""
},
"require": {
@@ -2181,7 +2288,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-main": "3.0-dev"
}
},
"autoload": {
@@ -2202,8 +2309,7 @@
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
- "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
- "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4"
},
"funding": [
{
@@ -2211,7 +2317,7 @@
"type": "github"
}
],
- "time": "2020-09-28T06:45:17+00:00"
+ "time": "2024-03-14T16:00:52+00:00"
},
{
"name": "sebastian/type",
@@ -2324,23 +2430,23 @@
},
{
"name": "seld/jsonlint",
- "version": "1.10.1",
+ "version": "1.11.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/jsonlint.git",
- "reference": "76d449a358ece77d6f1d6331c68453e657172202"
+ "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/76d449a358ece77d6f1d6331c68453e657172202",
- "reference": "76d449a358ece77d6f1d6331c68453e657172202",
+ "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2",
+ "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2",
"shasum": ""
},
"require": {
"php": "^5.3 || ^7.0 || ^8.0"
},
"require-dev": {
- "phpstan/phpstan": "^1.5",
+ "phpstan/phpstan": "^1.11",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13"
},
"bin": [
@@ -2372,7 +2478,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/jsonlint/issues",
- "source": "https://github.com/Seldaek/jsonlint/tree/1.10.1"
+ "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0"
},
"funding": [
{
@@ -2384,50 +2490,85 @@
"type": "tidelift"
}
],
- "time": "2023-12-18T13:03:25+00:00"
+ "time": "2024-07-11T14:55:45+00:00"
+ },
+ {
+ "name": "swoole/ide-helper",
+ "version": "4.8.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swoole/ide-helper.git",
+ "reference": "3ac4971814273889933b871e03b2a6b340e58f79"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swoole/ide-helper/zipball/3ac4971814273889933b871e03b2a6b340e58f79",
+ "reference": "3ac4971814273889933b871e03b2a6b340e58f79",
+ "shasum": ""
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Team Swoole",
+ "email": "team@swoole.com"
+ }
+ ],
+ "description": "IDE help files for Swoole.",
+ "support": {
+ "issues": "https://github.com/swoole/ide-helper/issues",
+ "source": "https://github.com/swoole/ide-helper/tree/4.8.3"
+ },
+ "funding": [
+ {
+ "url": "https://gitee.com/swoole/swoole?donate=true",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/swoole",
+ "type": "github"
+ }
+ ],
+ "time": "2021-12-01T08:11:40+00:00"
},
{
"name": "symfony/console",
- "version": "v7.0.1",
+ "version": "v8.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "cdce5c684b2f920bb1343deecdfba356ffad83d5"
+ "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/cdce5c684b2f920bb1343deecdfba356ffad83d5",
- "reference": "cdce5c684b2f920bb1343deecdfba356ffad83d5",
+ "url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
+ "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"shasum": ""
},
"require": {
- "php": ">=8.2",
- "symfony/polyfill-mbstring": "~1.0",
+ "php": ">=8.4",
+ "symfony/polyfill-mbstring": "^1.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^6.4|^7.0"
- },
- "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": {
@@ -2461,7 +2602,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.0.1"
+ "source": "https://github.com/symfony/console/tree/v8.0.7"
},
"funding": [
{
@@ -2472,25 +2613,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": "2023-12-01T15:10:06+00:00"
+ "time": "2026-03-06T14:06:22+00:00"
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.4.0",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
- "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": ""
},
"require": {
@@ -2498,12 +2643,12 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "3.4-dev"
- },
"thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -2528,7 +2673,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -2544,27 +2689,30 @@
"type": "tidelift"
}
],
- "time": "2023-05-23T14:45:45+00:00"
+ "time": "2024-09-25T14:21:43+00:00"
},
{
"name": "symfony/filesystem",
- "version": "v7.0.0",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7"
+ "reference": "7bf9162d7a0dff98d079b72948508fa48018a770"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/7da8ea2362a283771478c5f7729cfcb43a76b8b7",
- "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7",
+ "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": "^7.4|^8.0"
+ },
"type": "library",
"autoload": {
"psr-4": {
@@ -2591,7 +2739,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v7.0.0"
+ "source": "https://github.com/symfony/filesystem/tree/v8.0.6"
},
"funding": [
{
@@ -2602,32 +2750,36 @@
"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": "2023-07-27T06:33:22+00:00"
+ "time": "2026-02-25T16:59:43+00:00"
},
{
"name": "symfony/finder",
- "version": "v7.0.0",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56"
+ "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/6e5688d69f7cfc4ed4a511e96007e06c2d34ce56",
- "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56",
+ "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": {
@@ -2655,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.0.0"
+ "source": "https://github.com/symfony/finder/tree/v8.0.6"
},
"funding": [
{
@@ -2666,29 +2818,33 @@
"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": "2023-10-31T17:59:56+00:00"
+ "time": "2026-01-29T09:41:02+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v7.0.0",
+ "version": "v8.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
- "reference": "700ff4096e346f54cb628ea650767c8130f1001f"
+ "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f",
- "reference": "700ff4096e346f54cb628ea650767c8130f1001f",
+ "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",
@@ -2722,7 +2878,7 @@
"options"
],
"support": {
- "source": "https://github.com/symfony/options-resolver/tree/v7.0.0"
+ "source": "https://github.com/symfony/options-resolver/tree/v8.0.0"
},
"funding": [
{
@@ -2733,29 +2889,33 @@
"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": "2023-08-08T10:20:21+00:00"
+ "time": "2025-11-12T15:55:31+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.28.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
- "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@@ -2765,12 +2925,9 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.28-dev"
- },
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -2804,7 +2961,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
},
"funding": [
{
@@ -2815,41 +2972,42 @@
"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": "2023-01-26T09:26:14+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.28.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "875e90aeea2777b6f135677f618529449334a612"
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
- "reference": "875e90aeea2777b6f135677f618529449334a612",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.28-dev"
- },
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -2885,7 +3043,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
},
"funding": [
{
@@ -2896,41 +3054,42 @@
"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": "2023-01-26T09:26:14+00:00"
+ "time": "2025-06-27T09:58:17+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.28.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
- "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.28-dev"
- },
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -2969,7 +3128,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
},
"funding": [
{
@@ -2980,29 +3139,34 @@
"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": "2023-01-26T09:26:14+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.28.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "42292d99c55abe617799667f454222c54c60e229"
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
- "reference": "42292d99c55abe617799667f454222c54c60e229",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "ext-iconv": "*",
+ "php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@@ -3012,12 +3176,9 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.28-dev"
- },
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -3052,7 +3213,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
},
"funding": [
{
@@ -3063,29 +3224,33 @@
"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": "2023-07-28T09:04:16+00:00"
+ "time": "2024-12-23T08:48:59+00:00"
},
{
"name": "symfony/process",
- "version": "v7.0.0",
+ "version": "v8.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "13bdb1670c7f510494e04fcb2bfa29af63db9c0d"
+ "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/13bdb1670c7f510494e04fcb2bfa29af63db9c0d",
- "reference": "13bdb1670c7f510494e04fcb2bfa29af63db9c0d",
+ "url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
+ "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"shasum": ""
},
"require": {
- "php": ">=8.2"
+ "php": ">=8.4"
},
"type": "library",
"autoload": {
@@ -3113,7 +3278,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.0.0"
+ "source": "https://github.com/symfony/process/tree/v8.0.5"
},
"funding": [
{
@@ -3124,42 +3289,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": "2023-11-20T16:43:42+00:00"
+ "time": "2026-01-26T15:08:38+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.4.0",
+ "version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838"
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
- "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
"shasum": ""
},
"require": {
"php": ">=8.1",
- "psr/container": "^2.0"
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "3.4-dev"
- },
"thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -3195,7 +3365,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.4.0"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
},
"funding": [
{
@@ -3206,43 +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": "2023-07-30T20:28:31+00:00"
+ "time": "2025-07-15T11:30:57+00:00"
},
{
"name": "symfony/string",
- "version": "v7.0.0",
+ "version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620"
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
- "reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
+ "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/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": {
@@ -3281,7 +3455,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.0.0"
+ "source": "https://github.com/symfony/string/tree/v8.0.6"
},
"funding": [
{
@@ -3292,25 +3466,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": "2023-11-29T08:40:23+00:00"
+ "time": "2026-02-09T10:14:57+00:00"
},
{
"name": "theseer/tokenizer",
- "version": "1.2.2",
+ "version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
- "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
- "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
"shasum": ""
},
"require": {
@@ -3339,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.2"
+ "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
},
"funding": [
{
@@ -3347,20 +3525,20 @@
"type": "github"
}
],
- "time": "2023-11-20T00:12:19+00:00"
+ "time": "2025-11-17T20:03:58+00:00"
},
{
"name": "webmozart/glob",
- "version": "4.6.0",
+ "version": "4.7.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/glob.git",
- "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655"
+ "reference": "8a2842112d6916e61e0e15e316465b611f3abc17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozarts/glob/zipball/3c17f7dec3d9d0e87b575026011f2e75a56ed655",
- "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655",
+ "url": "https://api.github.com/repos/webmozarts/glob/zipball/8a2842112d6916e61e0e15e316465b611f3abc17",
+ "reference": "8a2842112d6916e61e0e15e316465b611f3abc17",
"shasum": ""
},
"require": {
@@ -3394,19 +3572,20 @@
"description": "A PHP implementation of Ant's glob.",
"support": {
"issues": "https://github.com/webmozarts/glob/issues",
- "source": "https://github.com/webmozarts/glob/tree/4.6.0"
+ "source": "https://github.com/webmozarts/glob/tree/4.7.0"
},
- "time": "2022-05-24T19:45:58+00:00"
+ "time": "2024-03-07T20:33:40+00:00"
}
],
"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 849fa5ec..322de5ba 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,10 +1,27 @@
-version: '3'
-
services:
- web:
- build: .
+ fpm:
+ build:
+ context: .
+ dockerfile: Dockerfile.fpm
ports:
- "9020:80"
volumes:
- ./src:/usr/share/nginx/html/src
- - ./tests:/usr/share/nginx/html/tests
\ No newline at end of file
+ - ./tests:/usr/share/nginx/html/tests
+ networks:
+ - testing
+ depends_on:
+ - swoole
+ swoole:
+ build:
+ context: .
+ dockerfile: Dockerfile.swoole
+ ports:
+ - "9501:80"
+ volumes:
+ - ./src:/usr/src/code/src
+ - ./tests:/usr/src/code/tests
+ networks:
+ - testing
+networks:
+ testing:
\ No newline at end of file
diff --git a/docs/Getting-Starting-Guide.md b/docs/Getting-Starting-Guide.md
index ab1779ac..86455959 100644
--- a/docs/Getting-Starting-Guide.md
+++ b/docs/Getting-Starting-Guide.md
@@ -1,50 +1,38 @@
-# Getting Started with Utopia-PHP
+# Getting Started with Utopia HTTP
# Intro
-Utopia Framework is an easy-to-use PHP MVC based framework with minimal must-have features for professional, simple, advanced and secure web development. It follows an architecture like Express and is based on the declarative programming approach. Documenting and writing code are usually seen as two separate tasks and very often, documentation loses priority in the software development lifecycle. Utopia unifies the two with a flexible API that allows your code to be self-documenting. What’s interesting about Utopia is its ability to accept metadata along with it’s route definitions. This metadata can then be used for various purposes like generating documentation, swagger specifications and more.
+Utopia HTTP is an easy-to-use PHP MVC based framework with minimal must-have features for professional, simple, advanced and secure web development. It follows an architecture like Express and is based on the declarative programming approach. Documenting and writing code are usually seen as two separate tasks and very often, documentation loses priority in the software development lifecycle. Utopia unifies the two with a flexible API that allows your code to be self-documenting. What’s interesting about Utopia is its ability to accept metadata along with it’s route definitions. This metadata can then be used for various purposes like generating documentation, swagger specifications and more.
# Defining Routes
-If you’re new to Utopia, let’s get started by looking at an example of a basic GET route for an application that you can create using Utopia. We'll be using a [Swoole server](https://github.com/swoole/swoole-src) in this example, but you should be able to extend it to any HTTP server.
+If you’re new to Utopia, let’s get started by looking at an example of a basic GET route for an application that you can create using Utopia. We'll be using a [Swoole server](https://github.com/swoole/swoole-src) in this example, but you should be able to extend it to any HTTP server.
## Basic GET Route
```php
-use Utopia\App;
-use Utopia\Swoole\Request;
-use Utopia\Swoole\Response;
-use Swoole\Http\Server;
-use Swoole\Http\Request as SwooleRequest;
-use Swoole\Http\Response as SwooleResponse;
+use Utopia\Http\Http;
+use Utopia\Http\Request;
+use Utopia\Http\Response;
+use Utopia\Http\Adapter\Swoole\Server;
-$http = new Server("0.0.0.0", 8080);
-
-App::get('/')
+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 app.
-*/
-
-$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
- $request = new Request($swooleRequest);
- $response = new Response($swooleResponse);
- $app = new App('America/Toronto');
- $app->run($request, $response);
-});
+ );
+$http = new Http(new Server("0.0.0.0", "8080"), 'America/Toronto');
$http->start();
```
-
+
Any route in Utopia would require you to `inject` the dependencies ( `$request` and `$response` in this case ) and define the controller by passing a callback to the `action` function. As you might have already guessed, `$request` and `$response` refer to the objects of the HTTP server library you’re using, for example, Swoole in this case. `action` defines the callback function that would be called when the GET request is executed. In this case, raw HTML is returned as a `$response`.
## More Endpoints
-You can perform basic CRUD operations like GET, POST, PUT and DELETE using Utopia. Let’s assume there's a file `todos.json` that stores a list of todo objects with the following structure. In a real-world scenario, you would be fetching this information from a database.
+You can perform basic CRUD operations like GET, POST, PUT and DELETE using Utopia. Let’s assume there's a file `todos.json` that stores a list of todo objects with the following structure. In a real-world scenario, you would be fetching this information from a database.
```json
[
@@ -59,14 +47,14 @@ You can perform basic CRUD operations like GET, POST, PUT and DELETE using Utopi
You can create a PUT request to update a todo by passing it’s reference `id` along with the values to be updated as follows:
```php
-App::put('/todos/:id')
+Http::put('/todos/:id')
->param('id', "", new Wildcard(), 'id of the todo')
->param('task', "", new Wildcard(), 'name of the todo')
->param('is_complete', true, new Wildcard(), 'task complete or not')
->inject('response')
->action(
function($id, $task, $is_complete, $response) {
- $path = \realpath('/app/app/todos.json');
+ $path = \realpath('/http/http/todos.json');
$data = json_decode(file_get_contents($path));
foreach($data as $object){
if($object->id == $id){
@@ -88,9 +76,9 @@ You might have noticed an additional property in the above example, i.e. `param`
All the parameters need to be defined using the `param` property which accepts the following - `$key`, `$default`, `$validator`, `$description`, `$optional` and `$injections`.
There are typically 3 types of parameters:
-1. Path params ( eg: `/api/users/` )
+1. Path params ( eg: `/api/users/` )
2. Query Params ( eg: `/api/users?userId=`)
-3. Body Params ( These are passed in the request body in POST and PUT requests. )
+3. Body Params ( These are passed in the request body in POST and PUT requests. )
Let's take a look at how these three types of params are taken care of by Utopia:
@@ -100,7 +88,7 @@ Let's take a look at how these three types of params are taken care of by Utopia
3. Body Parameters are specified using the `->param()` function as well.
-Each of these params then become available to the `->action()` callback function in the same order that they were declared in.
+Each of these params then become available to the `->action()` callback function in the same order that they were declared in.
### Returning a Response
Based on the type of the response you wish to return, multiple options can be used:
@@ -120,7 +108,7 @@ $response->json(['Goodbye' => 'World']);
JSON objects can be returned by passing the JSON object inside `$response->json()`.
-### Setting Response Status
+### Setting Response Status
You can set a status code for your response using the `setStatusCode()` function of utopia's response object.
@@ -130,7 +118,7 @@ $response
->send('')
```
-You can find the details of other status codes by visiting our [GitHub repository](https://github.com/utopia-php/framework/blob/master/src/Response.php).
+You can find the details of other status codes by visiting our [GitHub repository](https://github.com/utopia-php/http/blob/master/src/Response.php).
# Advanced Utopia
@@ -138,9 +126,9 @@ You can find the details of other status codes by visiting our [GitHub repositor
Let's make the above example slightly advanced by adding more properties.
```php
-use Utopia\App;
-use Utopia\Swoole\Request;
-use Utopia\Swoole\Response;
+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;
@@ -148,29 +136,29 @@ use Utopia\Validator\Wildcard;
$http = new Server("0.0.0.0", 8080);
-App::init(function($response) {
- /*
- Example of global init method. Do stuff that is common to all your endpoints in all groups.
+Http::init(function($response) {
+ /*
+ Example of global init method. Do stuff that is common to all your endpoints in all groups.
This can include things like authentication and authorisation checks, implementing rate limits and so on..
*/
}, ['response']);
-App::init(function($response) {
- /*
+Http::init(function($response) {
+ /*
Example of init method for group1. Do stuff that is common to all your endpoints in group1.
This can include things like authentication and authorisation checks, implementing rate limits and so on..
*/
}, ['response'], 'group1');
-App::init(function($response) {
- /*
- Example of init method for group2. Do stuff that is common to all your endpoints in group2.
+Http::init(function($response) {
+ /*
+ Example of init method for group2. Do stuff that is common to all your endpoints in group2.
This can include things like authentication and authorisation checks, implementing rate limits and so on..
*/
}, ['response'], 'group2');
-App::shutdown(function($request) {
- /*
+Http::shutdown(function($request) {
+ /*
Example of global shutdown method. Do stuff that needs to be performed at the end of each request for all groups.
'*' (Wildcard validator) is optional.
This can include cleanups, logging information, recording usage stats, closing database connections and so on..
@@ -178,15 +166,15 @@ App::shutdown(function($request) {
}, ['request'], '*');
-App::shutdown(function($request) {
- /*
+Http::shutdown(function($request) {
+ /*
Example of shutdown method of group1. Do stuff that needs to be performed at the end of each request for all groups.
This can include cleanups, logging information, recording usage stats, closing database connections and so on..
*/
}, ['request'], 'group1');
-App::put('/todos/:id')
+Http::put('/todos/:id')
->desc('Update todo')
->groups(['group1', 'group2'])
->label('scope', 'public')
@@ -197,7 +185,7 @@ App::put('/todos/:id')
->inject('response')
->action(
function($id, $task, $is_complete, $response) {
- $path = \realpath('/app/app/todos.json');
+ $path = \realpath('/http/http/todos.json');
$data = json_decode(file_get_contents($path));
foreach($data as $object){
if($object->id == $id){
@@ -211,7 +199,7 @@ App::put('/todos/:id')
$response->json($data);
}
);
-
+
$http->start();
```
@@ -224,10 +212,10 @@ For each endpoint, you can add the following properties described below. Let’s
`groups` are used to define common functions that need to be executed. When you add a callback function to a group, the init hooks of the respective group are executed before the individual actions are executed.
* #### Labels
-`label` can be used to store metadata that is related to your endpoint. It’s a key-value store. Some use-cases can be using label to generate the documentation or the swagger specifications.
+`label` can be used to store metadata that is related to your endpoint. It’s a key-value store. Some use-cases can be using label to generate the documentation or the swagger specifications.
* #### Injections
-Since each action in Utopia depends on certain resources, `inject` is used to add the dependencies. `$response` and `$request` can be injected into the service. Utopia provides the app static functions to make global resources available to all utopia endpoints.
+Since each action in Utopia depends on certain resources, `inject` is used to add the dependencies. `$response` and `$request` can be injected into the service. Utopia provides the http static functions to make global resources available to all utopia endpoints.
* #### Action
`action` contains the callback function that needs to be executed when an endpoint is called. The `param` and `inject` variables need to be passed as parameters in the callback function in the same order. The callback function defines the logic and also returns the `$response` back.
@@ -238,8 +226,8 @@ Since each action in Utopia depends on certain resources, `inject` is used to ad
Now that you’re familiar with routing in Utopia, let’s dive into the lifecycle of a utopia request in detail and learn about some of the lifecycle methods.
## Init and Shutdown Methods
-
-The Utopia app goes through the following lifecycle whenever it receives any request:
+
+The Utopia http goes through the following lifecycle whenever it receives any request:

@@ -248,17 +236,17 @@ In case an error occurs anywhere during the execution, the workflow executes the
The init and shutdown methods take three params:
1. Callback function
- 2. Array of resources required by the callback
+ 2. Array of resources required by the callback
3. The endpoint group for which the callback is intended to run
* ### Init
init method is executed in the beginning when the program execution begins. Here’s an example of the init method, where the init method is executed for all groups indicated by the wildcard symbol `'*'`.
```php
-App::init(function($response) {
- /*
- Do stuff that is common to all your endpoints.
- This can include things like authentication and authorisation checks, implementing rate limits and so on..
+Http::init(function($response) {
+ /*
+ Do stuff that is common to all your endpoints.
+ This can include things like authentication and authorisation checks, implementing rate limits and so on..
*/
}, ['response'], '*');
```
@@ -268,9 +256,9 @@ App::init(function($response) {
Utopia's shutdown callback is used to perform cleanup tasks after a request. This could include closing any open database connections, resetting certain flags, triggering analytics events (if any) and similar tasks.
```php
-App::shutdown(function($request) {
- /*
- Do stuff that needs to be performed at the end of each request.
+Http::shutdown(function($request) {
+ /*
+ Do stuff that needs to be performed at the end of each request.
This can include cleanups, logging information, recording usage stats, closing database connections and so on..
*/
@@ -279,9 +267,8 @@ App::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/framework dependency using `composer require utopia-php/framework` command.
-
-> Utopia Framework requires PHP 7.3 or later. We recommend using the latest PHP version whenever possible.
+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.
-Wonderful! 😄 You’re all set to create a basic demo app using the Utopia framework. If you have any issues or questions feel free to reach out to us on our [Discord Server](https://appwrite.io/discord).
+> 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/.gitignore b/example/.gitignore
new file mode 100644
index 00000000..5657f6ea
--- /dev/null
+++ b/example/.gitignore
@@ -0,0 +1 @@
+vendor
\ No newline at end of file
diff --git a/example/Dockerfile b/example/Dockerfile
new file mode 100644
index 00000000..2153e4e8
--- /dev/null
+++ b/example/Dockerfile
@@ -0,0 +1,12 @@
+FROM composer:2.0 AS step0
+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.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
+
+EXPOSE 80
+CMD ["php", "src/server.php"]
diff --git a/example/composer.json b/example/composer.json
new file mode 100644
index 00000000..383d90aa
--- /dev/null
+++ b/example/composer.json
@@ -0,0 +1,6 @@
+{
+ "name": "utopia-php/http-app",
+ "require": {
+ "utopia-php/http": "latest"
+ }
+}
\ No newline at end of file
diff --git a/example/docker-compose.yml b/example/docker-compose.yml
new file mode 100644
index 00000000..6733d969
--- /dev/null
+++ b/example/docker-compose.yml
@@ -0,0 +1,10 @@
+version: '3'
+
+services:
+ server:
+ build:
+ context: .
+ ports:
+ - "80:80"
+ volumes:
+ - ./src:/usr/src/code/src
\ No newline at end of file
diff --git a/example/src/server.php b/example/src/server.php
new file mode 100644
index 00000000..d05f80f2
--- /dev/null
+++ b/example/src/server.php
@@ -0,0 +1,18 @@
+param('name', 'World', new Text(256), 'Name to greet. Optional, max length 256.', true)
+ ->inject('response')
+ ->action(function (string $name, Response $response) {
+ $response->send('Hello ' . $name);
+ });
+
+$http = new Http(new Server('0.0.0.0', '80'), 'America/New_York');
+$http->start();
diff --git a/phpstan.neon b/phpstan.neon
index a76a8329..d275a391 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,4 +1,6 @@
parameters:
+ scanDirectories:
+ - vendor/swoole/ide-helper
level: 5
paths:
- src
diff --git a/phpunit.xml b/phpunit.xml
index de6deb0b..e33b301e 100755
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,17 +1,18 @@
-
+
./tests/e2e/Client.php
./tests/
-
\ No newline at end of file
+
diff --git a/src/Http/Adapter.php b/src/Http/Adapter.php
new file mode 100755
index 00000000..f72d6bfb
--- /dev/null
+++ b/src/Http/Adapter.php
@@ -0,0 +1,13 @@
+generateInput();
+
+ return $this->rawPayload;
+ }
+
+ /**
+ * Get server
+ *
+ * Method for querying server parameters. If $key is not found $default value will be returned.
+ *
+ * @param string $key
+ * @param string|null $default
+ * @return string|null
+ */
+ public function getServer(string $key, ?string $default = null): ?string
+ {
+ return $_SERVER[$key] ?? $default;
+ }
+
+ /**
+ * Set server
+ *
+ * Method for setting server parameters.
+ *
+ * @param string $key
+ * @param string $value
+ * @return static
+ */
+ public function setServer(string $key, string $value): static
+ {
+ $_SERVER[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get IP
+ *
+ * Returns users IP address.
+ * Support HTTP_X_FORWARDED_FOR header usually return
+ * from different proxy servers or PHP default REMOTE_ADDR
+ *
+ * @return string
+ */
+ public function getIP(): string
+ {
+ $ips = explode(',', $this->getHeader('HTTP_X_FORWARDED_FOR', $this->getServer('REMOTE_ADDR') ?? '0.0.0.0'));
+
+ return trim($ips[0] ?? '');
+ }
+
+ /**
+ * Get Protocol
+ *
+ * Returns request protocol.
+ * Support HTTP_X_FORWARDED_PROTO header usually return
+ * from different proxy servers or PHP default REQUEST_SCHEME
+ *
+ * @return string
+ */
+ public function getProtocol(): string
+ {
+ return $this->getServer('HTTP_X_FORWARDED_PROTO', $this->getServer('REQUEST_SCHEME')) ?? 'https';
+ }
+
+ /**
+ * Get Port
+ *
+ * Returns request port.
+ *
+ * @return string
+ */
+ public function getPort(): string
+ {
+ return (string) \parse_url($this->getProtocol().'://'.$this->getServer('HTTP_HOST', ''), PHP_URL_PORT);
+ }
+
+ /**
+ * Get Hostname
+ *
+ * Returns request hostname.
+ *
+ * @return string
+ */
+ public function getHostname(): string
+ {
+ $hostname = \parse_url($this->getProtocol().'://'.$this->getServer('HTTP_HOST', ''), PHP_URL_HOST);
+ return strtolower((string) ($hostname));
+ }
+
+ /**
+ * Get Method
+ *
+ * Return HTTP request method
+ *
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return $this->getServer('REQUEST_METHOD') ?? 'UNKNOWN';
+ }
+
+ /**
+ * Set Method
+ *
+ * Set HTTP request method
+ *
+ * @param string $method
+ * @return static
+ */
+ public function setMethod(string $method): static
+ {
+ $this->setServer('REQUEST_METHOD', $method);
+
+ return $this;
+ }
+
+ /**
+ * Get URI
+ *
+ * Return HTTP request URI
+ *
+ * @return string
+ */
+ public function getURI(): string
+ {
+ return $this->getServer('REQUEST_URI') ?? '';
+ }
+
+ /**
+ * Get Path
+ *
+ * Return HTTP request path
+ *
+ * @param string $uri
+ * @return static
+ */
+ public function setURI(string $uri): static
+ {
+ $this->setServer('REQUEST_URI', $uri);
+
+ return $this;
+ }
+
+ /**
+ * Get files
+ *
+ * Method for querying upload files data. If $key is not found empty array will be returned.
+ *
+ * @param string $key
+ * @return array
+ */
+ public function getFiles(string $key): array
+ {
+ return (isset($_FILES[$key])) ? $_FILES[$key] : [];
+ }
+
+ /**
+ * Get Referer
+ *
+ * Return HTTP referer header
+ *
+ * @param string $default
+ * @return string
+ */
+ public function getReferer(string $default = ''): string
+ {
+ return (string) $this->getServer('HTTP_REFERER', $default);
+ }
+
+ /**
+ * Get Origin
+ *
+ * Return HTTP origin header
+ *
+ * @param string $default
+ * @return string
+ */
+ public function getOrigin(string $default = ''): string
+ {
+ return (string) $this->getServer('HTTP_ORIGIN', $default);
+ }
+
+ /**
+ * Get User Agent
+ *
+ * Return HTTP user agent header
+ *
+ * @param string $default
+ * @return string
+ */
+ public function getUserAgent(string $default = ''): string
+ {
+ return (string) $this->getServer('HTTP_USER_AGENT', $default);
+ }
+
+ /**
+ * Get Accept
+ *
+ * Return HTTP accept header
+ *
+ * @param string $default
+ * @return string
+ */
+ public function getAccept(string $default = ''): string
+ {
+ return (string) $this->getServer('HTTP_ACCEPT', $default);
+ }
+
+ /**
+ * Get cookie
+ *
+ * Method for querying HTTP cookie parameters. If $key is not found $default value will be returned.
+ *
+ * @param string $key
+ * @param string $default
+ * @return string
+ */
+ public function getCookie(string $key, string $default = ''): string
+ {
+ return (isset($_COOKIE[$key])) ? $_COOKIE[$key] : $default;
+ }
+
+ /**
+ * Get header
+ *
+ * Method for querying HTTP header parameters. If $key is not found $default value will be returned.
+ *
+ * @param string $key
+ * @param string $default
+ * @return string
+ */
+ public function getHeader(string $key, string $default = ''): string
+ {
+ $headers = $this->generateHeaders();
+
+ return (isset($headers[$key])) ? $headers[$key] : $default;
+ }
+
+ /**
+ * Set header
+ *
+ * Method for adding HTTP header parameters.
+ *
+ * @param string $key
+ * @param string $value
+ * @return static
+ */
+ public function addHeader(string $key, string $value): static
+ {
+ $this->headers[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Remvoe header
+ *
+ * Method for removing HTTP header parameters.
+ *
+ * @param string $key
+ * @return static
+ */
+ public function removeHeader(string $key): static
+ {
+ if (isset($this->headers[$key])) {
+ unset($this->headers[$key]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Generate input
+ *
+ * Generate PHP input stream and parse it as an array in order to handle different content type of requests
+ *
+ * @return array
+ */
+ protected function generateInput(): array
+ {
+ if (null === $this->queryString) {
+ $this->queryString = $_GET;
+ }
+ if (null === $this->payload) {
+ $contentType = $this->getHeader('content-type');
+
+ // Get content-type without the charset
+ $length = \strpos($contentType, ';');
+ $length = (empty($length)) ? \strlen($contentType) : $length;
+ $contentType = \substr($contentType, 0, $length);
+
+ $this->rawPayload = \file_get_contents('php://input');
+
+ switch ($contentType) {
+ case 'application/json':
+ $this->payload = \json_decode($this->rawPayload, true);
+ break;
+ default:
+ $this->payload = $_POST;
+ break;
+ }
+
+ if (empty($this->payload)) { // Make sure we return same data type even if json payload is empty or failed
+ $this->payload = [];
+ }
+ }
+
+ return match ($this->getServer('REQUEST_METHOD', '')) {
+ self::METHOD_POST,
+ self::METHOD_PUT,
+ self::METHOD_PATCH,
+ self::METHOD_DELETE => $this->payload,
+ default => $this->queryString
+ };
+ }
+
+ /**
+ * Generate headers
+ *
+ * Parse request headers as an array for easy querying using the getHeader method
+ *
+ * @return array
+ */
+ protected function generateHeaders(): array
+ {
+ if (null === $this->headers) {
+ /**
+ * Fallback for older PHP versions
+ * that do not support generateHeaders
+ */
+ if (!\function_exists('getallheaders')) {
+ $headers = [];
+
+ foreach ($_SERVER as $name => $value) {
+ if (\substr($name, 0, 5) == 'HTTP_') {
+ $headers[\str_replace(' ', '-', \strtolower(\str_replace('_', ' ', \substr($name, 5))))] = $value;
+ }
+ }
+
+ $this->headers = $headers;
+
+ return $this->headers;
+ }
+
+ $this->headers = array_change_key_case(getallheaders());
+ }
+
+ return $this->headers;
+ }
+}
diff --git a/src/Http/Adapter/FPM/Response.php b/src/Http/Adapter/FPM/Response.php
new file mode 100644
index 00000000..6a5d4dd4
--- /dev/null
+++ b/src/Http/Adapter/FPM/Response.php
@@ -0,0 +1,89 @@
+ $value
+ * @return void
+ */
+ public function sendHeader(string $key, mixed $value): void
+ {
+ if (\is_array($value)) {
+ foreach ($value as $v) {
+ \header($key.': '.$v, false);
+ }
+ } else {
+ \header($key.': '.$value);
+ }
+ }
+
+ /**
+ * Send Cookie
+ *
+ * Output Cookie
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $options
+ * @return void
+ */
+ protected function sendCookie(string $name, string $value, array $options): void
+ {
+ // Use proper PHP keyword name
+ $options['expires'] = $options['expire'];
+ unset($options['expire']);
+
+ // Set the cookie
+ \setcookie($name, $value, $options);
+ }
+}
diff --git a/src/Http/Adapter/FPM/Server.php b/src/Http/Adapter/FPM/Server.php
new file mode 100755
index 00000000..d48d6fec
--- /dev/null
+++ b/src/Http/Adapter/FPM/Server.php
@@ -0,0 +1,39 @@
+container->set('fpmRequest', fn () => $request);
+ $this->container->set('fpmResponse', fn () => $response);
+
+ \call_user_func($callback, $request, $response);
+ }
+
+ public function onStart(callable $callback)
+ {
+ \call_user_func($callback, $this);
+ }
+
+ public function getContainer(): Container
+ {
+ return $this->container;
+ }
+
+ public function start()
+ {
+ return;
+ }
+}
diff --git a/src/Http/Adapter/Swoole/Request.php b/src/Http/Adapter/Swoole/Request.php
new file mode 100644
index 00000000..d5bd799c
--- /dev/null
+++ b/src/Http/Adapter/Swoole/Request.php
@@ -0,0 +1,382 @@
+swoole = $request;
+ }
+
+ /**
+ * Get raw payload
+ *
+ * Method for getting the HTTP request payload as a raw string.
+ *
+ * @return string
+ */
+ public function getRawPayload(): string
+ {
+ return $this->swoole->rawContent();
+ }
+
+ /**
+ * Get server
+ *
+ * Method for querying server parameters. If $key is not found $default value will be returned.
+ *
+ * @param string $key
+ * @param string|null $default
+ * @return string|null
+ */
+ public function getServer(string $key, ?string $default = null): ?string
+ {
+ return $this->swoole->server[$key] ?? $default;
+ }
+
+ /**
+ * Set server
+ *
+ * Method for setting server parameters.
+ *
+ * @param string $key
+ * @param string $value
+ * @return static
+ */
+ public function setServer(string $key, string $value): static
+ {
+ $this->swoole->server[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get IP
+ *
+ * Returns users IP address.
+ * Support HTTP_X_FORWARDED_FOR header usually return
+ * from different proxy servers or PHP default REMOTE_ADDR
+ */
+ public function getIP(): string
+ {
+ $ips = explode(',', $this->getHeader('x-forwarded-for', $this->getServer('remote_addr') ?? '0.0.0.0'));
+
+ return trim($ips[0] ?? '');
+ }
+
+ /**
+ * Get Protocol
+ *
+ * Returns request protocol.
+ * Support HTTP_X_FORWARDED_PROTO header usually return
+ * from different proxy servers or PHP default REQUEST_SCHEME
+ *
+ * @return string
+ */
+ public function getProtocol(): string
+ {
+ $protocol = $this->getHeader('x-forwarded-proto', $this->getServer('server_protocol') ?? 'https');
+
+ if ($protocol === 'HTTP/1.1') {
+ return 'http';
+ }
+
+ return match ($protocol) {
+ 'http', 'https', 'ws', 'wss' => $protocol,
+ default => 'https'
+ };
+ }
+
+ /**
+ * Get Port
+ *
+ * Returns request port.
+ *
+ * @return string
+ */
+ public function getPort(): string
+ {
+ return $this->getHeader('x-forwarded-port', (string) \parse_url($this->getProtocol().'://'.$this->getHeader('x-forwarded-host', $this->getHeader('host')), PHP_URL_PORT));
+ }
+
+ /**
+ * Get Hostname
+ *
+ * Returns request hostname.
+ *
+ * @return string
+ */
+ public function getHostname(): string
+ {
+ $hostname = \parse_url($this->getProtocol().'://'.$this->getHeader('x-forwarded-host', $this->getHeader('host')), PHP_URL_HOST);
+ return strtolower(strval($hostname));
+ }
+
+ /**
+ * Get Method
+ *
+ * Return HTTP request method
+ *
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return $this->getServer('request_method') ?? 'UNKNOWN';
+ }
+
+ /**
+ * Set method
+ *
+ * Set HTTP request method
+ *
+ * @param string $method
+ * @return static
+ */
+ public function setMethod(string $method): static
+ {
+ $this->setServer('request_method', $method);
+
+ return $this;
+ }
+
+ /**
+ * Get URI
+ *
+ * Return HTTP request URI
+ *
+ * @return string
+ */
+ public function getURI(): string
+ {
+ return $this->getServer('request_uri') ?? '';
+ }
+
+ /**
+ * Set URI
+ *
+ * Set HTTP request URI
+ *
+ * @param string $uri
+ * @return static
+ */
+ public function setURI(string $uri): static
+ {
+ $this->setServer('request_uri', $uri);
+
+ return $this;
+ }
+
+ /**
+ * Get Referer
+ *
+ * Return HTTP referer header
+ *
+ * @return string
+ */
+ public function getReferer(string $default = ''): string
+ {
+ return $this->getHeader('referer', '');
+ }
+
+ /**
+ * Get Origin
+ *
+ * Return HTTP origin header
+ *
+ * @return string
+ */
+ public function getOrigin(string $default = ''): string
+ {
+ return $this->getHeader('origin', $default);
+ }
+
+ /**
+ * Get User Agent
+ *
+ * Return HTTP user agent header
+ *
+ * @return string
+ */
+ public function getUserAgent(string $default = ''): string
+ {
+ return $this->getHeader('user-agent', $default);
+ }
+
+ /**
+ * Get Accept
+ *
+ * Return HTTP accept header
+ *
+ * @return string
+ */
+ public function getAccept(string $default = ''): string
+ {
+ return $this->getHeader('accept', $default);
+ }
+
+ /**
+ * Get files
+ *
+ * Method for querying upload files data. If $key is not found empty array will be returned.
+ *
+ * @param string $key
+ * @return array
+ */
+ public function getFiles($key): array
+ {
+ $key = strtolower($key);
+
+ return $this->swoole->files[$key] ?? [];
+ }
+
+ /**
+ * Get cookie
+ *
+ * Method for querying HTTP cookie parameters. If $key is not found $default value will be returned.
+ *
+ * @param string $key
+ * @param string $default
+ * @return string
+ */
+ public function getCookie(string $key, string $default = ''): string
+ {
+ $key = \strtolower($key);
+
+ $cookies = \explode(';', $this->getHeader('cookie', ''));
+ foreach ($cookies as $cookie) {
+ $cookie = \trim($cookie);
+ [$cookieKey, $cookieValue] = \explode('=', $cookie, 2);
+ $cookieKey = \trim($cookieKey);
+ $cookieValue = \trim($cookieValue);
+ if ($cookieKey === $key) {
+ return $cookieValue;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Get header
+ *
+ * Method for querying HTTP header parameters. If $key is not found $default value will be returned.
+ *
+ * @param string $key
+ * @param string $default
+ * @return string
+ */
+ public function getHeader(string $key, string $default = ''): string
+ {
+ return $this->swoole->header[$key] ?? $default;
+ }
+
+ /**
+ * Method for adding HTTP header parameters.
+ *
+ * @param string $key
+ * @param string $value
+ * @return static
+ */
+ public function addHeader(string $key, string $value): static
+ {
+ $this->swoole->header[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Method for removing HTTP header parameters.
+ *
+ * @param string $key
+ * @return static
+ */
+ public function removeHeader(string $key): static
+ {
+ if (isset($this->swoole->header[$key])) {
+ unset($this->swoole->header[$key]);
+ }
+
+ return $this;
+ }
+
+ public function getSwooleRequest(): SwooleRequest
+ {
+ return $this->swoole;
+ }
+
+ /**
+ * Generate input
+ *
+ * Generate PHP input stream and parse it as an array in order to handle different content type of requests
+ *
+ * @return array
+ */
+ protected function generateInput(): array
+ {
+ if (null === $this->queryString) {
+ $this->queryString = $this->swoole->get ?? [];
+ }
+ if (null === $this->payload) {
+ $contentType = $this->getHeader('content-type');
+
+ // Get content-type without the charset
+ $length = strpos($contentType, ';');
+ $length = (empty($length)) ? strlen($contentType) : $length;
+ $contentType = substr($contentType, 0, $length);
+
+ switch ($contentType) {
+ case 'application/json':
+ $this->payload = json_decode(strval($this->swoole->rawContent()), true);
+ break;
+
+ default:
+ $this->payload = $this->swoole->post;
+ break;
+ }
+
+ if (empty($this->payload)) { // Make sure we return same data type even if json payload is empty or failed
+ $this->payload = [];
+ }
+ }
+
+ return match ($this->getMethod()) {
+ self::METHOD_POST,
+ self::METHOD_PUT,
+ self::METHOD_PATCH,
+ self::METHOD_DELETE => $this->payload,
+ default => $this->queryString
+ };
+ }
+
+ /**
+ * Generate headers
+ *
+ * Parse request headers as an array for easy querying using the getHeader method
+ *
+ * @return array
+ */
+ protected function generateHeaders(): array
+ {
+ $headers = $this->swoole->header;
+
+ foreach ($headers as $key => $value) {
+ $headers[strtolower($key)] = $value;
+ }
+
+ return $headers;
+ }
+}
diff --git a/src/Http/Adapter/Swoole/Response.php b/src/Http/Adapter/Swoole/Response.php
new file mode 100644
index 00000000..be48a027
--- /dev/null
+++ b/src/Http/Adapter/Swoole/Response.php
@@ -0,0 +1,99 @@
+swoole = $response;
+ parent::__construct(\microtime(true));
+ }
+
+ public function getSwooleResponse(): SwooleResponse
+ {
+ return $this->swoole;
+ }
+
+ /**
+ * Write
+ *
+ * @param string $content
+ * @return bool False if write cannot complete, such as request ended by client
+ */
+ public function write(string $content): bool
+ {
+ return $this->swoole->write($content);
+ }
+
+ /**
+ * End
+ *
+ * @param string|null $content
+ * @return void
+ */
+ public function end(?string $content = null): void
+ {
+ $this->swoole->end($content);
+ }
+
+ /**
+ * Send Status Code
+ *
+ * @param int $statusCode
+ * @return void
+ */
+ protected function sendStatus(int $statusCode): void
+ {
+ $this->swoole->status((string) $statusCode);
+ }
+
+ /**
+ * Send Header
+ *
+ * @param string $key
+ * @param string|array $value
+ * @return void
+ */
+ public function sendHeader(string $key, mixed $value): void
+ {
+ $this->swoole->header($key, $value);
+ }
+
+ /**
+ * Send Cookie
+ *
+ * Send a cookie
+ *
+ * @param string $name
+ * @param string $value
+ * @param array $options
+ * @return void
+ */
+ protected function sendCookie(string $name, string $value, array $options): void
+ {
+ $this->swoole->cookie(
+ name: $name,
+ value: $value,
+ expires: $options['expire'] ?? 0,
+ path: $options['path'] ?? '',
+ domain: $options['domain'] ?? '',
+ secure: $options['secure'] ?? false,
+ httponly: $options['httponly'] ?? false,
+ samesite: $options['samesite'] ?? false,
+ );
+ }
+}
diff --git a/src/Http/Adapter/Swoole/Server.php b/src/Http/Adapter/Swoole/Server.php
new file mode 100755
index 00000000..57b4f5fb
--- /dev/null
+++ b/src/Http/Adapter/Swoole/Server.php
@@ -0,0 +1,65 @@
+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) {
+ $requestContainer = new Container($this->container);
+ $requestContainer->set('swooleRequest', fn () => $request);
+ $requestContainer->set('swooleResponse', fn () => $response);
+
+ Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] = $requestContainer;
+
+ $utopiaRequest = new Request($request);
+ $utopiaResponse = new Response($response);
+
+ \call_user_func($callback, $utopiaRequest, $utopiaResponse);
+ });
+ }
+
+ public function getContainer(): Container
+ {
+ return Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? $this->container;
+ }
+
+ public function onStart(callable $callback)
+ {
+
+ \call_user_func($callback, $this);
+ }
+
+ public function start()
+ {
+ if (Coroutine::getCid() === -1) {
+ run(fn () => $this->server->start());
+ } else {
+ $this->server->start();
+ }
+ }
+}
diff --git a/src/Exception.php b/src/Http/Exception.php
similarity index 67%
rename from src/Exception.php
rename to src/Http/Exception.php
index 71eaf127..46e55958 100644
--- a/src/Exception.php
+++ b/src/Http/Exception.php
@@ -1,6 +1,6 @@
+ */
+ protected array $loaded = [];
+
+ /**
+ * @var int
+ */
+ protected int $count = 0;
+
+ /**
+ * @var array
+ */
+ protected array $mimeTypes = [];
+
+ /**
+ * @var array
+ */
+ public const EXTENSIONS = [
+ 'css' => 'text/css',
+ 'js' => 'text/javascript',
+ 'svg' => 'image/svg+xml',
+ ];
+
+ /**
+ * Add MIME type.
+ *
+ * @param string $mimeType
+ * @return void
+ */
+ public function addMimeType(string $mimeType): void
+ {
+ $this->mimeTypes[$mimeType] = true;
+ }
+
+ /**
+ * Remove MIME type.
+ *
+ * @param string $mimeType
+ * @return void
+ */
+ public function removeMimeType(string $mimeType): void
+ {
+ if (isset($this->mimeTypes[$mimeType])) {
+ unset($this->mimeTypes[$mimeType]);
+ }
+ }
+
+ /**
+ * Get MimeType List
+ *
+ * @return array
+ */
+ public function getMimeTypes(): array
+ {
+ return $this->mimeTypes;
+ }
+
+ /**
+ * Get Files Loaded Count
+ *
+ * @return int
+ */
+ public function getCount(): int
+ {
+ return $this->count;
+ }
+
+ /**
+ * Load directory.
+ *
+ * @param string $directory
+ * @param string|null $root
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function load(string $directory, ?string $root = null): void
+ {
+ if (!is_readable($directory)) {
+ throw new Exception("Failed to load directory: {$directory}");
+ }
+
+ $directory = realpath($directory);
+
+ $root ??= $directory;
+
+ $handle = opendir(strval($directory));
+
+ while ($path = readdir($handle)) {
+ $extension = pathinfo($path, PATHINFO_EXTENSION);
+
+ if (in_array($path, ['.', '..'])) {
+ continue;
+ }
+
+ if (in_array($extension, ['php', 'phtml'])) {
+ continue;
+ }
+
+ if (substr($path, 0, 1) === '.') {
+ continue;
+ }
+
+ $dirPath = $directory.'/'.$path;
+
+ if (is_dir($dirPath)) {
+ $this->load($dirPath, strval($root));
+
+ continue;
+ }
+
+ $key = substr($dirPath, strlen(strval($root)));
+
+ if (array_key_exists($key, $this->loaded)) {
+ continue;
+ }
+
+ $this->loaded[$key] = [
+ 'contents' => file_get_contents($dirPath),
+ 'mimeType' => (array_key_exists($extension, self::EXTENSIONS))
+ ? self::EXTENSIONS[$extension]
+ : mime_content_type($dirPath),
+ ];
+
+ $this->count++;
+ }
+
+ closedir($handle);
+ }
+
+ /**
+ * Is file loaded.
+ *
+ * @param string $uri
+ * @return bool
+ */
+ public function isFileLoaded(string $uri): bool
+ {
+ if (!array_key_exists($uri, $this->loaded)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get file contents.
+ *
+ * @param string $uri
+ * @return string
+ *
+ * @throws \Exception
+ */
+ public function getFileContents(string $uri): mixed
+ {
+ if (!array_key_exists($uri, $this->loaded)) {
+ throw new Exception('File not found or not loaded: '.$uri);
+ }
+
+ return $this->loaded[$uri]['contents'];
+ }
+
+ /**
+ * Get file MIME type.
+ *
+ * @param string $uri
+ * @return string
+ *
+ * @throws \Exception
+ */
+ public function getFileMimeType(string $uri): mixed
+ {
+ if (!array_key_exists($uri, $this->loaded)) {
+ throw new Exception('File not found or not loaded: '.$uri);
+ }
+
+ return $this->loaded[$uri]['mimeType'];
+ }
+
+ /**
+ * Reset.
+ *
+ * @return void
+ */
+ public function reset(): void
+ {
+ $this->count = 0;
+ $this->loaded = [];
+ $this->mimeTypes = [];
+ }
+}
diff --git a/src/Hook.php b/src/Http/Hook.php
similarity index 99%
rename from src/Hook.php
rename to src/Http/Hook.php
index 4b933528..0f177dc3 100644
--- a/src/Hook.php
+++ b/src/Http/Hook.php
@@ -1,6 +1,8 @@
null,
- ];
+ protected Files $files;
- /**
- * @var array
- */
- protected static array $resourcesCallbacks = [];
+ protected Container $container;
+
+ protected ?Container $requestContainer = null;
/**
* Current running mode
@@ -85,6 +85,20 @@ class App
*/
protected static array $options = [];
+ /**
+ * Server Start hooks
+ *
+ * @var Hook[]
+ */
+ protected static array $startHooks = [];
+
+ /**
+ * Request hooks
+ *
+ * @var Hook[]
+ */
+ protected static array $requestHooks = [];
+
/**
* Route
*
@@ -103,13 +117,21 @@ class App
protected static ?Route $wildcardRoute = null;
/**
- * App
+ * @var Adapter
+ */
+ protected Adapter $server;
+
+ /**
+ * Http
*
+ * @param Adapter $server
* @param string $timezone
*/
- public function __construct(string $timezone)
+ public function __construct(Adapter $server, string $timezone)
{
\date_default_timezone_set($timezone);
+ $this->files = new Files();
+ $this->server = $server;
}
/**
@@ -268,7 +290,7 @@ public static function error(): Hook
* @param string|null $default
* @return string|null
*/
- public static function getEnv(string $key, string $default = null): ?string
+ public static function getEnv(string $key, ?string $default = null): ?string
{
return $_SERVER[$key] ?? $default;
}
@@ -322,41 +344,33 @@ 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, bool $fresh = false): mixed
+ public function getResource(string $name): mixed
{
- if ($name === 'utopia') {
- return $this;
- }
+ 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) || $fresh || self::$resourcesCallbacks[$name]['reset']) {
- if (!\array_key_exists($name, self::$resourcesCallbacks)) {
- throw new Exception('Failed to find resource: "' . $name . '"');
+ if ($message === $e->getMessage() && !\str_contains($message, 'resource')) {
+ $message = 'Failed to find resource: "' . $name . '"';
}
- $this->resources[$name] = \call_user_func_array(
- self::$resourcesCallbacks[$name]['callback'],
- $this->getResources(self::$resourcesCallbacks[$name]['injections'])
- );
+ throw new Exception($message, 500, $e);
}
-
- self::$resourcesCallbacks[$name]['reset'] = false;
-
- return $this->resources[$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): array
{
@@ -370,25 +384,17 @@ public function getResources(array $list): array
}
/**
- * Set a new resource callback
+ * Set a resource on the given scope.
*
- * @param string $name
- * @param callable $callback
- * @param array $injections
- * @return void
- *
- * @throws Exception
+ * @param string[] $injections
*/
- public static function setResource(string $name, callable $callback, array $injections = []): 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[$name] = ['callback' => $callback, 'injections' => $injections, 'reset' => true];
+ $this->server->getContainer()->set($name, $callback, $injections);
}
/**
- * Is app in production mode?
+ * Is http in production mode?
*
* @return bool
*/
@@ -398,7 +404,7 @@ public static function isProduction(): bool
}
/**
- * Is app in development mode?
+ * Is http in development mode?
*
* @return bool
*/
@@ -408,7 +414,7 @@ public static function isDevelopment(): bool
}
/**
- * Is app in stage mode?
+ * Is http in stage mode?
*
* @return bool
*/
@@ -469,6 +475,107 @@ public static function addRoute(string $method, string $url): Route
return $route;
}
+ /**
+ * Load directory.
+ *
+ * @param string $directory
+ * @param string|null $root
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function loadFiles(string $directory, ?string $root = null): void
+ {
+ $this->files->load($directory, $root);
+ }
+
+ /**
+ * Is file loaded.
+ *
+ * @param string $uri
+ * @return bool
+ */
+ protected function isFileLoaded(string $uri): bool
+ {
+ return $this->files->isFileLoaded($uri);
+ }
+
+ /**
+ * Get file contents.
+ *
+ * @param string $uri
+ * @return string
+ *
+ * @throws \Exception
+ */
+ protected function getFileContents(string $uri): mixed
+ {
+ return $this->files->getFileContents($uri);
+ }
+
+ /**
+ * Get file MIME type.
+ *
+ * @param string $uri
+ * @return string
+ *
+ * @throws \Exception
+ */
+ protected function getFileMimeType(string $uri): mixed
+ {
+ return $this->files->getFileMimeType($uri);
+ }
+
+ public static function onStart(): Hook
+ {
+ $hook = new Hook();
+ self::$startHooks[] = $hook;
+ return $hook;
+ }
+
+ public static function onRequest(): Hook
+ {
+ $hook = new Hook();
+ self::$requestHooks[] = $hook;
+ return $hook;
+ }
+
+ public function start()
+ {
+
+ $this->server->onRequest(
+ fn (Request $request, Response $response) => $this->run($request, $response)
+ );
+
+ $this->server->onStart(function ($server) {
+ $this->setResource('server', function () use ($server) {
+ return $server;
+ });
+ try {
+
+ foreach (self::$startHooks as $hook) {
+ $arguments = $this->getArguments($hook, [], []);
+ \call_user_func_array($hook->getAction(), $arguments);
+ }
+ } catch (\Exception $e) {
+ $this->setResource('error', fn () => $e);
+
+ foreach (self::$errors as $error) { // Global error hooks
+ if (in_array('*', $error->getGroups())) {
+ try {
+ $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);
+ }
+ }
+ }
+ }
+ });
+
+ $this->server->start();
+ }
+
/**
* Match
*
@@ -478,7 +585,7 @@ public static function addRoute(string $method, string $url): Route
* @param bool $fresh If true, will not match any cached route
* @return null|Route
*/
- public function match(Request $request, bool $fresh = false): ?Route
+ public function match(Request $request, bool $fresh = true): ?Route
{
if (null !== $this->route && !$fresh) {
return $this->route;
@@ -499,7 +606,7 @@ public function match(Request $request, bool $fresh = false): ?Route
* @param Route $route
* @param Request $request
*/
- public function execute(Route $route, Request $request, Response $response): static
+ public function execute(Route $route, Request $request): static
{
$arguments = [];
$groups = $route->getGroups();
@@ -517,24 +624,19 @@ public function execute(Route $route, Request $request, Response $response): sta
foreach ($groups as $group) {
foreach (self::$init as $hook) { // Group init hooks
- if (in_array($group, $hook->getGroups())) {
+ if (\in_array($group, $hook->getGroups())) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}
- if (!($response->isSent())) {
- $arguments = $this->getArguments($route, $pathValues, $request->getParams());
-
- // Call the action callback with the matched positions as params
- \call_user_func_array($route->getAction(), $arguments);
- }
-
+ $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())) {
+ if (\in_array($group, $hook->getGroups())) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
@@ -543,18 +645,18 @@ public function execute(Route $route, Request $request, Response $response): sta
if ($route->getHook()) {
foreach (self::$shutdown as $hook) { // Group shutdown hooks
- if (in_array('*', $hook->getGroups())) {
+ 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);
+ $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, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
@@ -566,7 +668,7 @@ public function execute(Route $route, Request $request, Response $response): sta
}
foreach (self::$errors as $error) { // Global error hooks
- if (in_array('*', $error->getGroups())) {
+ if (\in_array('*', $error->getGroups())) {
try {
$arguments = $this->getArguments($error, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
@@ -599,6 +701,9 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
$paramExists = $existsInRequest || $existsInValues;
$arg = $existsInRequest ? $requestParams[$key] : $param['default'];
+ if (\is_callable($arg) && !\is_string($arg)) {
+ $arg = \call_user_func_array($arg, $this->getResources($param['injections']));
+ }
$value = $existsInValues ? $values[$key] : $arg;
if (!$param['skipValidation']) {
@@ -628,26 +733,52 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
* This is the place to initialize any pre routing logic.
* This is where you might want to parse the application current URL by any desired logic
*
- * @param Request $request
- * @param Response $response
+ * @param Request $request
+ * @param Response $response;
*/
public function run(Request $request, Response $response): static
{
- $this->resources['request'] = $request;
- $this->resources['response'] = $response;
+ $this->setResource('request', fn () => $request);
+ $this->setResource('response', fn () => $response);
- self::setResource('request', function () use ($request) {
- return $request;
- });
+ try {
+ foreach (self::$requestHooks as $hook) {
+ $arguments = $this->getArguments($hook, [], []);
+ \call_user_func_array($hook->getAction(), $arguments);
+ }
+ } catch (\Exception $e) {
+ $this->setResource('error', fn () => $e, []);
- self::setResource('response', function () use ($response) {
- return $response;
- });
+ foreach (self::$errors as $error) { // Global error hooks
+ if (\in_array('*', $error->getGroups())) {
+ try {
+ $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);
+ }
+ }
+ }
+ }
+
+ if ($this->isFileLoaded($request->getURI())) {
+ $time = (60 * 60 * 24 * 365 * 2); // 45 days cache
+
+ $response
+ ->setContentType($this->getFileMimeType($request->getURI()))
+ ->addHeader('Cache-Control', 'public, max-age=' . $time)
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
+ ->send($this->getFileContents($request->getURI()));
+
+ return $this;
+ }
$method = $request->getMethod();
$route = $this->match($request);
$groups = ($route instanceof Route) ? $route->getGroups() : [];
+ $this->setResource('route', fn () => $route, []);
+
if (self::REQUEST_METHOD_HEAD == $method) {
$method = self::REQUEST_METHOD_GET;
$response->disablePayload();
@@ -658,7 +789,7 @@ public function run(Request $request, Response $response): static
foreach ($groups as $group) {
foreach (self::$options as $option) { // Group options hooks
/** @var Hook $option */
- if (in_array($group, $option->getGroups())) {
+ if (\in_array($group, $option->getGroups())) {
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
}
}
@@ -666,17 +797,17 @@ public function run(Request $request, Response $response): static
foreach (self::$options as $option) { // Global options hooks
/** @var Hook $option */
- if (in_array('*', $option->getGroups())) {
+ 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;
- });
+ }, []);
\call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
}
}
@@ -690,41 +821,41 @@ public function run(Request $request, Response $response): static
$this->route = $route;
$path = \parse_url($request->getURI(), PHP_URL_PATH);
$route->path($path);
+
+ $this->setResource('route', fn () => $route, []);
}
if (null !== $route) {
- return $this->execute($route, $request, $response);
+ 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())) {
+ 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())) {
+ 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;
- });
+ }, []);
\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);
- });
+ 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()));
}
}
@@ -733,6 +864,7 @@ public function run(Request $request, Response $response): static
return $this;
}
+
/**
* Validate Param
*
@@ -774,11 +906,13 @@ protected function validate(string $key, array $param, mixed $value): void
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/Request.php b/src/Http/Request.php
similarity index 74%
rename from src/Request.php
rename to src/Http/Request.php
index b877920a..c9762123 100755
--- a/src/Request.php
+++ b/src/Http/Request.php
@@ -1,8 +1,8 @@
generateInput();
-
- return $this->rawPayload;
- }
+ abstract public function getRawPayload(): string;
/**
* Get server
@@ -136,10 +124,7 @@ public function getRawPayload(): string
* @param string|null $default
* @return string|null
*/
- public function getServer(string $key, string $default = null): ?string
- {
- return $_SERVER[$key] ?? $default;
- }
+ abstract public function getServer(string $key, ?string $default = null): ?string;
/**
* Set server
@@ -150,12 +135,7 @@ public function getServer(string $key, string $default = null): ?string
* @param string $value
* @return static
*/
- public function setServer(string $key, string $value): static
- {
- $_SERVER[$key] = $value;
-
- return $this;
- }
+ abstract public function setServer(string $key, string $value): static;
/**
* Get IP
@@ -166,12 +146,7 @@ public function setServer(string $key, string $value): static
*
* @return string
*/
- public function getIP(): string
- {
- $ips = explode(',', $this->getHeader('HTTP_X_FORWARDED_FOR', $this->getServer('REMOTE_ADDR') ?? '0.0.0.0'));
-
- return trim($ips[0] ?? '');
- }
+ abstract public function getIP(): string;
/**
* Get Protocol
@@ -182,10 +157,7 @@ public function getIP(): string
*
* @return string
*/
- public function getProtocol(): string
- {
- return $this->getServer('HTTP_X_FORWARDED_PROTO', $this->getServer('REQUEST_SCHEME')) ?? 'https';
- }
+ abstract public function getProtocol(): string;
/**
* Get Port
@@ -194,10 +166,7 @@ public function getProtocol(): string
*
* @return string
*/
- public function getPort(): string
- {
- return (string) \parse_url($this->getProtocol().'://'.$this->getServer('HTTP_HOST', ''), PHP_URL_PORT);
- }
+ abstract public function getPort(): string;
/**
* Get Hostname
@@ -206,10 +175,7 @@ public function getPort(): string
*
* @return string
*/
- public function getHostname(): string
- {
- return (string) \parse_url($this->getProtocol().'://'.$this->getServer('HTTP_HOST', ''), PHP_URL_HOST);
- }
+ abstract public function getHostname(): string;
/**
* Get Method
@@ -218,10 +184,7 @@ public function getHostname(): string
*
* @return string
*/
- public function getMethod(): string
- {
- return $this->getServer('REQUEST_METHOD') ?? 'UNKNOWN';
- }
+ abstract public function getMethod(): string;
/**
* Set Method
@@ -231,12 +194,7 @@ public function getMethod(): string
* @param string $method
* @return static
*/
- public function setMethod(string $method): static
- {
- $this->setServer('REQUEST_METHOD', $method);
-
- return $this;
- }
+ abstract public function setMethod(string $method): static;
/**
* Get URI
@@ -258,12 +216,7 @@ public function getURI(): string
* @param string $uri
* @return static
*/
- public function setURI(string $uri): static
- {
- $this->setServer('REQUEST_URI', $uri);
-
- return $this;
- }
+ abstract public function setURI(string $uri): static;
/**
* Get files
@@ -273,10 +226,7 @@ public function setURI(string $uri): static
* @param string $key
* @return array
*/
- public function getFiles(string $key): array
- {
- return (isset($_FILES[$key])) ? $_FILES[$key] : [];
- }
+ abstract public function getFiles(string $key): array;
/**
* Get Referer
@@ -286,10 +236,7 @@ public function getFiles(string $key): array
* @param string $default
* @return string
*/
- public function getReferer(string $default = ''): string
- {
- return (string) $this->getServer('HTTP_REFERER', $default);
- }
+ abstract public function getReferer(string $default = ''): string;
/**
* Get Origin
@@ -299,10 +246,7 @@ public function getReferer(string $default = ''): string
* @param string $default
* @return string
*/
- public function getOrigin(string $default = ''): string
- {
- return (string) $this->getServer('HTTP_ORIGIN', $default);
- }
+ abstract public function getOrigin(string $default = ''): string;
/**
* Get User Agent
@@ -312,10 +256,7 @@ public function getOrigin(string $default = ''): string
* @param string $default
* @return string
*/
- public function getUserAgent(string $default = ''): string
- {
- return (string) $this->getServer('HTTP_USER_AGENT', $default);
- }
+ abstract public function getUserAgent(string $default = ''): string;
/**
* Get Accept
@@ -325,10 +266,7 @@ public function getUserAgent(string $default = ''): string
* @param string $default
* @return string
*/
- public function getAccept(string $default = ''): string
- {
- return (string) $this->getServer('HTTP_ACCEPT', $default);
- }
+ abstract public function getAccept(string $default = ''): string;
/**
* Get cookie
@@ -339,10 +277,7 @@ public function getAccept(string $default = ''): string
* @param string $default
* @return string
*/
- public function getCookie(string $key, string $default = ''): string
- {
- return (isset($_COOKIE[$key])) ? $_COOKIE[$key] : $default;
- }
+ abstract public function getCookie(string $key, string $default = ''): string;
/**
* Get header
@@ -353,12 +288,7 @@ public function getCookie(string $key, string $default = ''): string
* @param string $default
* @return string
*/
- public function getHeader(string $key, string $default = ''): string
- {
- $headers = $this->generateHeaders();
-
- return (isset($headers[$key])) ? $headers[$key] : $default;
- }
+ abstract public function getHeader(string $key, string $default = ''): string;
/**
* Get headers
@@ -381,12 +311,7 @@ public function getHeaders(): array
* @param string $value
* @return static
*/
- public function addHeader(string $key, string $value): static
- {
- $this->headers[$key] = $value;
-
- return $this;
- }
+ abstract public function addHeader(string $key, string $value): static;
/**
* Remvoe header
@@ -396,14 +321,7 @@ public function addHeader(string $key, string $value): static
* @param string $key
* @return static
*/
- public function removeHeader(string $key): static
- {
- if (isset($this->headers[$key])) {
- unset($this->headers[$key]);
- }
-
- return $this;
- }
+ abstract public function removeHeader(string $key): static;
/**
* Get Request Size
@@ -414,7 +332,16 @@ 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');
}
/**
@@ -562,51 +489,6 @@ public function setPayload(array $params): static
return $this;
}
- /**
- * Generate input
- *
- * Generate PHP input stream and parse it as an array in order to handle different content type of requests
- *
- * @return array
- */
- protected function generateInput(): array
- {
- if (null === $this->queryString) {
- $this->queryString = $_GET;
- }
- if (null === $this->payload) {
- $contentType = $this->getHeader('content-type');
-
- // Get content-type without the charset
- $length = \strpos($contentType, ';');
- $length = (empty($length)) ? \strlen($contentType) : $length;
- $contentType = \substr($contentType, 0, $length);
-
- $this->rawPayload = \file_get_contents('php://input');
-
- switch ($contentType) {
- case 'application/json':
- $this->payload = \json_decode($this->rawPayload, true);
- break;
- default:
- $this->payload = $_POST;
- break;
- }
-
- if (empty($this->payload)) { // Make sure we return same data type even if json payload is empty or failed
- $this->payload = [];
- }
- }
-
- return match ($this->getServer('REQUEST_METHOD', '')) {
- self::METHOD_POST,
- self::METHOD_PUT,
- self::METHOD_PATCH,
- self::METHOD_DELETE => $this->payload,
- default => $this->queryString
- };
- }
-
/**
* Generate headers
*
@@ -641,6 +523,15 @@ protected function generateHeaders(): array
return $this->headers;
}
+ /**
+ * Generate input
+ *
+ * Generate PHP input stream and parse it as an array in order to handle different content type of requests
+ *
+ * @return array
+ */
+ abstract protected function generateInput(): array;
+
/**
* Content Range Parser
*
diff --git a/src/Response.php b/src/Http/Response.php
similarity index 80%
rename from src/Response.php
rename to src/Http/Response.php
index b9c7ba84..444bc4f8 100755
--- a/src/Response.php
+++ b/src/Http/Response.php
@@ -1,8 +1,8 @@
'Continue',
self::STATUS_CODE_SWITCHING_PROTOCOLS => 'Switching Protocols',
+ self::STATUS_CODE_PROCESSING => 'Processing',
+ self::STATUS_CODE_EARLY_HINTS => 'Early Hints',
self::STATUS_CODE_OK => 'OK',
self::STATUS_CODE_CREATED => 'Created',
self::STATUS_CODE_ACCEPTED => 'Accepted',
@@ -140,6 +124,9 @@ class Response
self::STATUS_CODE_NOCONTENT => 'No Content',
self::STATUS_CODE_RESETCONTENT => 'Reset Content',
self::STATUS_CODE_PARTIALCONTENT => 'Partial Content',
+ self::STATUS_CODE_MULTI_STATUS => 'Multi-Status',
+ self::STATUS_CODE_ALREADY_REPORTED => 'Already Reported',
+ self::STATUS_CODE_IM_USED => 'IM Used',
self::STATUS_CODE_MULTIPLE_CHOICES => 'Multiple Choices',
self::STATUS_CODE_MOVED_PERMANENTLY => 'Moved Permanently',
self::STATUS_CODE_FOUND => 'Found',
@@ -148,6 +135,7 @@ class Response
self::STATUS_CODE_USE_PROXY => 'Use Proxy',
self::STATUS_CODE_UNUSED => '(Unused)',
self::STATUS_CODE_TEMPORARY_REDIRECT => 'Temporary Redirect',
+ self::STATUS_CODE_PERMANENT_REDIRECT => 'Permanent Redirect',
self::STATUS_CODE_BAD_REQUEST => 'Bad Request',
self::STATUS_CODE_UNAUTHORIZED => 'Unauthorized',
self::STATUS_CODE_PAYMENT_REQUIRED => 'Payment Required',
@@ -166,14 +154,28 @@ class Response
self::STATUS_CODE_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type',
self::STATUS_CODE_REQUESTED_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable',
self::STATUS_CODE_EXPECTATION_FAILED => 'Expectation Failed',
+ self::STATUS_CODE_IM_A_TEAPOT => 'I\'m a teapot',
+ self::STATUS_CODE_MISDIRECTED_REQUEST => 'Misdirected Request',
+ self::STATUS_CODE_UNPROCESSABLE_ENTITY => 'Unprocessable Entity',
+ self::STATUS_CODE_LOCKED => 'Locked',
+ self::STATUS_CODE_FAILED_DEPENDENCY => 'Failed Dependency',
self::STATUS_CODE_TOO_EARLY => 'Too Early',
+ self::STATUS_CODE_UPGRADE_REQUIRED => 'Upgrade Required',
+ self::STATUS_CODE_PRECONDITION_REQUIRED => 'Precondition Required',
self::STATUS_CODE_TOO_MANY_REQUESTS => 'Too Many Requests',
+ self::STATUS_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large',
+ self::STATUS_CODE_UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons',
self::STATUS_CODE_INTERNAL_SERVER_ERROR => 'Internal Server Error',
self::STATUS_CODE_NOT_IMPLEMENTED => 'Not Implemented',
self::STATUS_CODE_BAD_GATEWAY => 'Bad Gateway',
self::STATUS_CODE_SERVICE_UNAVAILABLE => 'Service Unavailable',
self::STATUS_CODE_GATEWAY_TIMEOUT => 'Gateway Timeout',
self::STATUS_CODE_HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version Not Supported',
+ self::STATUS_CODE_VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates',
+ self::STATUS_CODE_INSUFFICIENT_STORAGE => 'Insufficient Storage',
+ self::STATUS_CODE_LOOP_DETECTED => 'Loop Detected',
+ self::STATUS_CODE_NOT_EXTENDED => 'Not Extended',
+ self::STATUS_CODE_NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required',
];
/**
@@ -222,8 +224,10 @@ class Response
*/
protected bool $sent = false;
+ protected bool $headersSent = false;
+
/**
- * @var array
+ * @var array>
*/
protected array $headers = [];
@@ -363,7 +367,15 @@ public function enablePayload(): static
*/
public function addHeader(string $key, string $value): static
{
- $this->headers[$key] = $value;
+ if (\array_key_exists($key, $this->headers)) {
+ if (\is_array($this->headers[$key])) {
+ $this->headers[$key][] = $value;
+ } else {
+ $this->headers[$key] = [$this->headers[$key], $value];
+ }
+ } else {
+ $this->headers[$key] = $value;
+ }
return $this;
}
@@ -389,7 +401,7 @@ public function removeHeader(string $key): static
*
* Return array of all response headers
*
- * @return array
+ * @return array>
*/
public function getHeaders(): array
{
@@ -402,15 +414,15 @@ public function getHeaders(): array
* Add an HTTP cookie to response header
*
* @param string $name
- * @param string $value
- * @param int $expire
- * @param string $path
- * @param string $domain
- * @param bool $secure
- * @param bool $httponly
- * @param string $sameSite
- */
- public function addCookie(string $name, string $value = null, int $expire = null, string $path = null, string $domain = null, bool $secure = null, bool $httponly = null, string $sameSite = null): static
+ * @param string|null $value
+ * @param int|null $expire
+ * @param string|null $path
+ * @param string|null $domain
+ * @param bool|null $secure
+ * @param bool|null $httponly
+ * @param string|null $sameSite
+ */
+ public function addCookie(string $name, ?string $value = null, ?int $expire = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httponly = null, ?string $sameSite = null): static
{
$name = strtolower($name);
@@ -478,10 +490,24 @@ public function send(string $body = ''): void
->appendCookies()
->appendHeaders();
+ $this->headersSent = true;
+
if (!$this->disablePayload) {
$length = strlen($body);
- $this->size = $this->size + strlen(implode("\n", $this->headers)) + $length;
+ $headersSize = 0;
+ foreach ($this->headers as $name => $values) {
+ if (\is_array($values)) {
+ foreach ($values as $value) {
+ $headersSize += \strlen($name . ': ' . $value);
+ }
+ $headersSize += (\count($values) - 1) * 2; // linebreaks
+ } else {
+ $headersSize += \strlen($name . ': ' . $values);
+ }
+ }
+ $headersSize += (\count($this->headers) - 1) * 2; // linebreaks
+ $this->size = $this->size + $headersSize + $length;
if (array_key_exists(
$this->contentType,
@@ -508,27 +534,19 @@ public function send(string $body = ''): void
* Send output
*
* @param string $content
- * @return void
+ * @return bool False if write cannot complete, such as request ended by client
*/
- protected function write(string $content): void
- {
- echo $content;
- }
+ abstract public function write(string $content): bool;
/**
* End
*
* Send optional content and end
*
- * @param string $content
+ * @param string|null $content
* @return void
*/
- protected function end(string $content = null): void
- {
- if (!is_null($content)) {
- echo $content;
- }
- }
+ abstract public function end(?string $content = null): void;
/**
* Output response
@@ -552,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);
@@ -597,10 +619,7 @@ protected function appendHeaders(): static
* @param int $statusCode
* @return void
*/
- protected function sendStatus(int $statusCode): void
- {
- http_response_code($statusCode);
- }
+ abstract protected function sendStatus(int $statusCode): void;
/**
* Send Header
@@ -608,13 +627,10 @@ protected function sendStatus(int $statusCode): void
* Output Header
*
* @param string $key
- * @param string $value
+ * @param string|array $value
* @return void
*/
- protected function sendHeader(string $key, string $value): void
- {
- \header($key.': '.$value);
- }
+ abstract public function sendHeader(string $key, mixed $value): void;
/**
* Send Cookie
@@ -626,15 +642,7 @@ protected function sendHeader(string $key, string $value): void
* @param array $options
* @return void
*/
- protected function sendCookie(string $name, string $value, array $options): void
- {
- // Use proper PHP keyword name
- $options['expires'] = $options['expire'];
- unset($options['expire']);
-
- // Set the cookie
- \setcookie($name, $value, $options);
- }
+ abstract protected function sendCookie(string $name, string $value, array $options): void;
/**
* Append cookies
diff --git a/src/Route.php b/src/Http/Route.php
similarity index 97%
rename from src/Route.php
rename to src/Http/Route.php
index fa4b25fa..8a7d0620 100755
--- a/src/Route.php
+++ b/src/Http/Route.php
@@ -1,6 +1,6 @@
*/
protected static array $routes = [
- App::REQUEST_METHOD_GET => [],
- App::REQUEST_METHOD_POST => [],
- App::REQUEST_METHOD_PUT => [],
- App::REQUEST_METHOD_PATCH => [],
- App::REQUEST_METHOD_DELETE => [],
+ Http::REQUEST_METHOD_GET => [],
+ Http::REQUEST_METHOD_POST => [],
+ Http::REQUEST_METHOD_PUT => [],
+ Http::REQUEST_METHOD_PATCH => [],
+ Http::REQUEST_METHOD_DELETE => [],
];
/**
@@ -69,7 +69,7 @@ public static function setAllowOverride(bool $value): void
/**
* Add route to router.
*
- * @param \Utopia\Route $route
+ * @param \Utopia\Http\Route $route
* @return void
* @throws \Exception
*/
@@ -95,7 +95,7 @@ public static function addRoute(Route $route): void
/**
* Add route to router.
*
- * @param \Utopia\Route $route
+ * @param \Utopia\Http\Route $route
* @return void
* @throws \Exception
*/
@@ -115,7 +115,7 @@ public static function addRouteAlias(string $path, Route $route): void
*
* @param string $method
* @param string $path
- * @return \Utopia\Route|null
+ * @return \Utopia\Http\Route|null
*/
public static function match(string $method, string $path): Route|null
{
@@ -226,11 +226,11 @@ public static function reset(): void
{
self::$params = [];
self::$routes = [
- App::REQUEST_METHOD_GET => [],
- App::REQUEST_METHOD_POST => [],
- App::REQUEST_METHOD_PUT => [],
- App::REQUEST_METHOD_PATCH => [],
- App::REQUEST_METHOD_DELETE => [],
+ Http::REQUEST_METHOD_GET => [],
+ Http::REQUEST_METHOD_POST => [],
+ Http::REQUEST_METHOD_PUT => [],
+ Http::REQUEST_METHOD_PATCH => [],
+ Http::REQUEST_METHOD_DELETE => [],
];
}
}
diff --git a/src/Validator.php b/src/Validator.php
deleted file mode 100755
index d58dfea7..00000000
--- a/src/Validator.php
+++ /dev/null
@@ -1,57 +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/Validator/Assoc.php b/src/Validator/Assoc.php
deleted file mode 100644
index 52618682..00000000
--- a/src/Validator/Assoc.php
+++ /dev/null
@@ -1,73 +0,0 @@
- 65535) {
- return false;
- }
-
- return \array_keys($value) !== \range(0, \count($value) - 1);
- }
-}
diff --git a/src/Validator/Boolean.php b/src/Validator/Boolean.php
deleted file mode 100644
index 50c1c986..00000000
--- a/src/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/Validator/Domain.php b/src/Validator/Domain.php
deleted file mode 100644
index aea0a21c..00000000
--- a/src/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/Validator/HexColor.php b/src/Validator/HexColor.php
deleted file mode 100644
index 9a45f4c7..00000000
--- a/src/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;
- }
-
- if (\in_array(\parse_url($value, PHP_URL_HOST), $this->whitelist)) {
- return true;
- }
-
- 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/Validator/Hostname.php b/src/Validator/Hostname.php
deleted file mode 100644
index b26d62da..00000000
--- a/src/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/Validator/IP.php b/src/Validator/IP.php
deleted file mode 100644
index 69e1f95c..00000000
--- a/src/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/Validator/Integer.php b/src/Validator/Integer.php
deleted file mode 100755
index ed885218..00000000
--- a/src/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/Validator/JSON.php b/src/Validator/JSON.php
deleted file mode 100644
index a3bf88a3..00000000
--- a/src/Validator/JSON.php
+++ /dev/null
@@ -1,59 +0,0 @@
-addRule($rule);
- }
-
- $this->type = $type;
- }
- /**
- * Add rule
- *
- * Add a new rule to the end of the rules containing array
- *
- * @param Validator $rule
- * @return $this
- */
- public function addRule(Validator $rule)
- {
- $this->rules[] = $rule;
-
- return $this;
- }
-
- /**
- * Get Description
- *
- * Returns validator description
- *
- * @return string
- */
- public function getDescription(): string
- {
- $description = '';
- foreach ($this->rules as $key => $rule) {
- $description .= ++$key . '. ' . $rule->getDescription() . " \n";
- }
-
- 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->rules as $rule) { /* @var $rule Validator */
- if (false === $rule->isValid($value)) {
- 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/Validator/Nullable.php b/src/Validator/Nullable.php
deleted file mode 100644
index 3d0d8724..00000000
--- a/src/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/Validator/Numeric.php b/src/Validator/Numeric.php
deleted file mode 100755
index e3db46c0..00000000
--- a/src/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/Validator/Text.php b/src/Validator/Text.php
deleted file mode 100644
index 8be82d4f..00000000
--- a/src/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/Validator/URL.php b/src/Validator/URL.php
deleted file mode 100644
index 1c120086..00000000
--- a/src/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/Validator/WhiteList.php b/src/Validator/WhiteList.php
deleted file mode 100755
index b374bb4f..00000000
--- a/src/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/Validator/Wildcard.php b/src/Validator/Wildcard.php
deleted file mode 100644
index d8c47795..00000000
--- a/src/Validator/Wildcard.php
+++ /dev/null
@@ -1,62 +0,0 @@
-setPath($path) method
- *
- * @param string $path
- *
- * @throws Exception
- */
- public function __construct(string $path = '')
- {
- $this->setPath($path);
-
- $this
- ->addFilter(self::FILTER_ESCAPE, function (string $value) {
- return \htmlentities($value, ENT_QUOTES, 'UTF-8');
- })
- ->addFilter(self::FILTER_NL2P, function (string $value) {
- $paragraphs = '';
-
- foreach (\explode("\n\n", $value) as $line) {
- if (\trim($line)) {
- $paragraphs .= ''.$line.'
';
- }
- }
-
- $paragraphs = \str_replace("\n", '
', $paragraphs);
-
- return $paragraphs;
- });
- }
-
- /**
- * Set param
- *
- * Assign a parameter by key
- *
- * @param string $key
- * @param mixed $value
- *
- * @throws Exception
- */
- public function setParam(string $key, mixed $value, bool $escapeHtml = true): static
- {
- if (\strpos($key, '.') !== false) {
- throw new Exception('$key can\'t contain a dot "." character');
- }
-
- if (is_string($value) && $escapeHtml) {
- $value = \htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
- }
-
- $this->params[$key] = $value;
-
- return $this;
- }
-
- /**
- * Set parent View object conatining this object
- *
- * @param self $view
- */
- public function setParent(self $view): static
- {
- $this->parent = $view;
-
- return $this;
- }
-
- /**
- * Return a View instance of the parent view containing this view
- *
- * @return self|null
- */
- public function getParent(): ?self
- {
- if (!empty($this->parent)) {
- return $this->parent;
- }
-
- return null;
- }
-
- /**
- * Get param
- *
- * Returns an assigned parameter by its key or $default if param key doesn't exists
- *
- * @param string $path
- * @param mixed $default (optional)
- * @return mixed
- */
- public function getParam(string $path, mixed $default = null): mixed
- {
- $path = \explode('.', $path);
- $temp = $this->params;
-
- foreach ($path as $key) {
- $temp = (isset($temp[$key])) ? $temp[$key] : null;
-
- if (null !== $temp) {
- $value = $temp;
- } else {
- return $default;
- }
- }
-
- return $value;
- }
-
- /**
- * Set path
- *
- * Set object template path that will be used to render view output
- *
- * @param string $path
- *
- * @throws Exception
- */
- public function setPath(string $path): static
- {
- $this->path = $path;
-
- return $this;
- }
-
- /**
- * Set rendered
- *
- * By enabling rendered state to true, the object will not render its template and will return an empty string instead
- *
- * @param bool $state
- */
- public function setRendered(bool $state = true): static
- {
- $this->rendered = $state;
-
- return $this;
- }
-
- /**
- * Is rendered
- *
- * Return whether current View rendering state is set to true or false
- *
- * @return bool
- */
- public function isRendered(): bool
- {
- return $this->rendered;
- }
-
- /**
- * Add Filter
- *
- * @param string $name
- * @param callable $callback
- */
- public function addFilter(string $name, callable $callback): static
- {
- $this->filters[$name] = $callback;
-
- return $this;
- }
-
- /**
- * Output and filter value
- *
- * @param mixed $value
- * @param string|array $filter
- * @return mixed
- *
- * @throws Exception
- */
- public function print(mixed $value, string|array $filter = ''): mixed
- {
- if (!empty($filter)) {
- if (\is_array($filter)) {
- foreach ($filter as $callback) {
- if (!isset($this->filters[$callback])) {
- throw new Exception('Filter "'.$callback.'" is not registered');
- }
-
- $value = $this->filters[$callback]($value);
- }
- } else {
- if (!isset($this->filters[$filter])) {
- throw new Exception('Filter "'.$filter.'" is not registered');
- }
-
- $value = $this->filters[$filter]($value);
- }
- }
-
- return $value;
- }
-
- /**
- * Render
- *
- * Render view .phtml template file if template has not been set as rendered yet using $this->setRendered(true).
- * In case path is not readable throws Exception.
- *
- * @param bool $minify
- * @return string
- *
- * @throws Exception
- */
- public function render(bool $minify = true): string
- {
- if ($this->rendered) { // Don't render any template
- return '';
- }
-
- \ob_start(); //Start of build
-
- if (\is_readable($this->path)) {
- /**
- * Include template file
- *
- * @psalm-suppress UnresolvableInclude
- */
- include $this->path;
- } else {
- \ob_end_clean();
- throw new Exception('"'.$this->path.'" view template is not readable');
- }
-
- $html = \ob_get_contents();
-
- \ob_end_clean(); //End of build
-
- if ($minify) {
- // Searching textarea and pre
- \preg_match_all('#\.*\<\/textarea\>#Uis', $html, $foundTxt);
- \preg_match_all('#\.*\<\/pre\>#Uis', $html, $foundPre);
-
- // replacing both with / $index
- $html = \str_replace($foundTxt[0], \array_map(function ($el) {
- return '';
- }, \array_keys($foundTxt[0])), $html);
- $html = \str_replace($foundPre[0], \array_map(function ($el) {
- return ''.$el.'
';
- }, \array_keys($foundPre[0])), $html);
-
- // your stuff
- $search = [
- '/\>[^\S ]+/s', // strip whitespaces after tags, except space
- '/[^\S ]+\',
- '<',
- '\\1',
- ];
-
- $html = \preg_replace($search, $replace, $html);
-
- // Replacing back with content
- $html = \str_replace(\array_map(function ($el) {
- return '';
- }, \array_keys($foundTxt[0])), $foundTxt[0], $html);
- $html = \str_replace(\array_map(function ($el) {
- return ''.$el.'
';
- }, \array_keys($foundPre[0])), $foundPre[0], $html);
- }
-
- return $html;
- }
-
- /* View Helpers */
-
- /**
- * Exec
- *
- * Exec child View components
- *
- * @param array|self $view
- * @return string
- *
- * @throws Exception
- */
- public function exec($view): string
- {
- $output = '';
-
- if (\is_array($view)) {
- foreach ($view as $node) { /* @var $node self */
- if ($node instanceof self) {
- $node->setParent($this);
- $output .= $node->render();
- }
- }
- }
-
- if ($view instanceof self) {
- $view->setParent($this);
- $output = $view->render();
- }
-
- return $output;
- }
-}
diff --git a/tests/HookTest.php b/tests/HookTest.php
index 10164818..49a0afd5 100644
--- a/tests/HookTest.php
+++ b/tests/HookTest.php
@@ -1,6 +1,6 @@
app = new App('Asia/Tel_Aviv');
+ Http::reset();
+ $this->container = new Container();
+ $this->http = new Http(new Server($this->container), 'Asia/Tel_Aviv');
$this->saveRequest();
}
public function tearDown(): void
{
- $this->app = null;
+ $this->http = null;
+ $this->container = null;
$this->restoreRequest();
}
@@ -42,31 +49,31 @@ protected function restoreRequest(): void
public function testCanGetDifferentModes(): void
{
- $this->assertEmpty(App::getMode());
- $this->assertFalse(App::isProduction());
- $this->assertFalse(App::isDevelopment());
- $this->assertFalse(App::isStage());
+ $this->assertEmpty(Http::getMode());
+ $this->assertFalse(Http::isProduction());
+ $this->assertFalse(Http::isDevelopment());
+ $this->assertFalse(Http::isStage());
- App::setMode(App::MODE_TYPE_PRODUCTION);
+ Http::setMode(Http::MODE_TYPE_PRODUCTION);
- $this->assertEquals(App::MODE_TYPE_PRODUCTION, App::getMode());
- $this->assertTrue(App::isProduction());
- $this->assertFalse(App::isDevelopment());
- $this->assertFalse(App::isStage());
+ $this->assertEquals(Http::MODE_TYPE_PRODUCTION, Http::getMode());
+ $this->assertTrue(Http::isProduction());
+ $this->assertFalse(Http::isDevelopment());
+ $this->assertFalse(Http::isStage());
- App::setMode(App::MODE_TYPE_DEVELOPMENT);
+ Http::setMode(Http::MODE_TYPE_DEVELOPMENT);
- $this->assertEquals(App::MODE_TYPE_DEVELOPMENT, App::getMode());
- $this->assertFalse(App::isProduction());
- $this->assertTrue(App::isDevelopment());
- $this->assertFalse(App::isStage());
+ $this->assertEquals(Http::MODE_TYPE_DEVELOPMENT, Http::getMode());
+ $this->assertFalse(Http::isProduction());
+ $this->assertTrue(Http::isDevelopment());
+ $this->assertFalse(Http::isStage());
- App::setMode(App::MODE_TYPE_STAGE);
+ Http::setMode(Http::MODE_TYPE_STAGE);
- $this->assertEquals(App::MODE_TYPE_STAGE, App::getMode());
- $this->assertFalse(App::isProduction());
- $this->assertFalse(App::isDevelopment());
- $this->assertTrue(App::isStage());
+ $this->assertEquals(Http::MODE_TYPE_STAGE, Http::getMode());
+ $this->assertFalse(Http::isProduction());
+ $this->assertFalse(Http::isDevelopment());
+ $this->assertTrue(Http::isStage());
}
public function testCanGetEnvironmentVariable(): void
@@ -74,53 +81,16 @@ public function testCanGetEnvironmentVariable(): void
// Mock
$_SERVER['key'] = 'value';
- $this->assertEquals(App::getEnv('key'), 'value');
- $this->assertEquals(App::getEnv('unknown', 'test'), 'test');
- }
-
- public function testCanGetResources(): void
- {
- App::setResource('rand', fn () => rand());
- App::setResource('first', fn ($second) => "first-{$second}", ['second']);
- App::setResource('second', fn () => 'second');
-
- $second = $this->app->getResource('second');
- $first = $this->app->getResource('first');
- $this->assertEquals('second', $second);
- $this->assertEquals('first-second', $first);
-
- $resource = $this->app->getResource('rand');
-
- $this->assertNotEmpty($resource);
- $this->assertEquals($resource, $this->app->getResource('rand'));
- $this->assertEquals($resource, $this->app->getResource('rand'));
- $this->assertEquals($resource, $this->app->getResource('rand'));
-
- // 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->app->execute($route, new Request(), new Response());
- $result = \ob_get_contents();
- \ob_end_clean();
-
- $this->assertEquals('x-def-y-def-' . $resource, $result);
+ $this->assertEquals(Http::getEnv('key'), 'value');
+ $this->assertEquals(Http::getEnv('unknown', 'test'), 'test');
}
public function testCanExecuteRoute(): void
{
- App::setResource('rand', fn () => rand());
- $resource = $this->app->getResource('rand');
+ $this->container->set('rand', fn () => rand());
+ $resource = $this->container->get('rand');
- $this->app
+ $this->http
->error()
->inject('error')
->action(function ($error) {
@@ -138,12 +108,12 @@ public function testCanExecuteRoute(): void
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
// With Params
-
+ $resource = $this->container->get('rand');
$route = new Route('GET', '/path');
$route
@@ -160,16 +130,16 @@ public function testCanExecuteRoute(): void
});
\ob_start();
- $request = new UtopiaRequestTest();
+ $request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y', 'z' => 'param-z']);
- $this->app->execute($route, $request, new Response());
+ $this->http->execute($route, $request);
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals($resource . '-param-x-param-y', $result);
// With Error
-
+ $resource = $this->container->get('rand');
$route = new Route('GET', '/path');
$route
@@ -180,51 +150,51 @@ public function testCanExecuteRoute(): void
});
\ob_start();
- $request = new UtopiaRequestTest();
+ $request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y']);
- $this->app->execute($route, $request, new Response());
+ $this->http->execute($route, $request);
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals('error: Invalid `x` param: Value must be a valid string and no longer than 1 chars', $result);
// With Hooks
-
- $this->app
+ $resource = $this->container->get('rand');
+ $this->http
->init()
->inject('rand')
->action(function ($rand) {
echo 'init-' . $rand . '-';
});
- $this->app
+ $this->http
->shutdown()
->action(function () {
echo '-shutdown';
});
- $this->app
+ $this->http
->init()
->groups(['api'])
->action(function () {
echo '(init-api)-';
});
- $this->app
+ $this->http
->shutdown()
->groups(['api'])
->action(function () {
echo '-(shutdown-api)';
});
- $this->app
+ $this->http
->init()
->groups(['homepage'])
->action(function () {
echo '(init-homepage)-';
});
- $this->app
+ $this->http
->shutdown()
->groups(['homepage'])
->action(function () {
@@ -252,18 +222,19 @@ public function testCanExecuteRoute(): void
});
\ob_start();
- $request = new UtopiaRequestTest();
+ $request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y']);
- $this->app->execute($route, $request, new Response());
+ $this->http->execute($route, $request);
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals('init-' . $resource . '-(init-api)-param-x-param-y-(shutdown-api)-shutdown', $result);
+ $resource = $this->container->get('rand');
\ob_start();
- $request = new UtopiaRequestTest();
+ $request = new UtopiaFPMRequestTest();
$request::_setParams(['x' => 'param-x', 'y' => 'param-y']);
- $this->app->execute($homepage, $request, new Response());
+ $this->http->execute($homepage, $request);
$result = \ob_get_contents();
\ob_end_clean();
@@ -272,13 +243,13 @@ public function testCanExecuteRoute(): void
public function testCanAddAndExecuteHooks()
{
- $this->app
+ $this->http
->init()
->action(function () {
echo '(init)-';
});
- $this->app
+ $this->http
->shutdown()
->action(function () {
echo '-(shutdown)';
@@ -293,7 +264,7 @@ public function testCanAddAndExecuteHooks()
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -309,7 +280,7 @@ public function testCanAddAndExecuteHooks()
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -318,51 +289,51 @@ public function testCanAddAndExecuteHooks()
public function testAllowRouteOverrides()
{
- App::setAllowOverride(false);
- $this->assertFalse(App::getAllowOverride());
- App::get('/')->action(function () {
+ Http::setAllowOverride(false);
+ $this->assertFalse(Http::getAllowOverride());
+ Http::get('/')->action(function () {
echo 'Hello first';
});
try {
- App::get('/')->action(function () {
+ Http::get('/')->action(function () {
echo 'Hello second';
});
$this->fail('Failed to throw exception');
- } catch (Exception $e) {
+ } catch (\Exception $e) {
// Threw exception as expected
$this->assertEquals('Route for (GET:) already registered.', $e->getMessage());
}
// Test success
- App::setAllowOverride(true);
- $this->assertTrue(App::getAllowOverride());
- App::get('/')->action(function () {
+ Http::setAllowOverride(true);
+ $this->assertTrue(Http::getAllowOverride());
+ Http::get('/')->action(function () {
echo 'Hello first';
});
- App::get('/')->action(function () {
+ Http::get('/')->action(function () {
echo 'Hello second';
});
}
public function testCanHookThrowExceptions()
{
- $this->app
+ $this->http
->init()
->param('y', '', new Text(5), 'y param', false)
->action(function ($y) {
echo '(init)-' . $y . '-';
});
- $this->app
+ $this->http
->error()
->inject('error')
->action(function ($error) {
echo 'error-' . $error->getMessage();
});
- $this->app
+ $this->http
->shutdown()
->action(function () {
echo '-(shutdown)';
@@ -377,7 +348,7 @@ public function testCanHookThrowExceptions()
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -385,7 +356,7 @@ public function testCanHookThrowExceptions()
\ob_start();
$_GET['y'] = 'y-def';
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
@@ -396,60 +367,60 @@ public function testCanSetRoute()
{
$route = new Route('GET', '/path');
- $this->assertEquals($this->app->getRoute(), null);
- $this->app->setRoute($route);
- $this->assertEquals($this->app->getRoute(), $route);
+ $this->assertEquals($this->http->getRoute(), null);
+ $this->http->setRoute($route);
+ $this->assertEquals($this->http->getRoute(), $route);
}
public function providerRouteMatching(): array
{
return [
- 'GET request' => [App::REQUEST_METHOD_GET, '/path1'],
- 'GET request on different route' => [App::REQUEST_METHOD_GET, '/path2'],
- 'GET request with trailing slash #1' => [App::REQUEST_METHOD_GET, '/path3', '/path3/'],
- 'GET request with trailing slash #2' => [App::REQUEST_METHOD_GET, '/path3/', '/path3/'],
- 'GET request with trailing slash #3' => [App::REQUEST_METHOD_GET, '/path3/', '/path3'],
- 'POST request' => [App::REQUEST_METHOD_POST, '/path1'],
- 'PUT request' => [App::REQUEST_METHOD_PUT, '/path1'],
- 'PATCH request' => [App::REQUEST_METHOD_PATCH, '/path1'],
- 'DELETE request' => [App::REQUEST_METHOD_DELETE, '/path1'],
- '1 separators' => [App::REQUEST_METHOD_GET, '/a/'],
- '2 separators' => [App::REQUEST_METHOD_GET, '/a/b'],
- '3 separators' => [App::REQUEST_METHOD_GET, '/a/b/c']
+ 'GET request' => [Http::REQUEST_METHOD_GET, '/path1'],
+ 'GET request on different route' => [Http::REQUEST_METHOD_GET, '/path2'],
+ 'GET request with trailing slash #1' => [Http::REQUEST_METHOD_GET, '/path3', '/path3/'],
+ 'GET request with trailing slash #2' => [Http::REQUEST_METHOD_GET, '/path3/', '/path3/'],
+ 'GET request with trailing slash #3' => [Http::REQUEST_METHOD_GET, '/path3/', '/path3'],
+ 'POST request' => [Http::REQUEST_METHOD_POST, '/path1'],
+ 'PUT request' => [Http::REQUEST_METHOD_PUT, '/path1'],
+ 'PATCH request' => [Http::REQUEST_METHOD_PATCH, '/path1'],
+ 'DELETE request' => [Http::REQUEST_METHOD_DELETE, '/path1'],
+ '1 separators' => [Http::REQUEST_METHOD_GET, '/a/'],
+ '2 separators' => [Http::REQUEST_METHOD_GET, '/a/b'],
+ '3 separators' => [Http::REQUEST_METHOD_GET, '/a/b/c']
];
}
/**
* @dataProvider providerRouteMatching
*/
- public function testCanMatchRoute(string $method, string $path, string $url = null): void
+ public function testCanMatchRoute(string $method, string $path, ?string $url = null): void
{
$url ??= $path;
$expected = null;
switch ($method) {
- case App::REQUEST_METHOD_GET:
- $expected = App::get($path);
+ case Http::REQUEST_METHOD_GET:
+ $expected = Http::get($path);
break;
- case App::REQUEST_METHOD_POST:
- $expected = App::post($path);
+ case Http::REQUEST_METHOD_POST:
+ $expected = Http::post($path);
break;
- case App::REQUEST_METHOD_PUT:
- $expected = App::put($path);
+ case Http::REQUEST_METHOD_PUT:
+ $expected = Http::put($path);
break;
- case App::REQUEST_METHOD_PATCH:
- $expected = App::patch($path);
+ case Http::REQUEST_METHOD_PATCH:
+ $expected = Http::patch($path);
break;
- case App::REQUEST_METHOD_DELETE:
- $expected = App::delete($path);
+ case Http::REQUEST_METHOD_DELETE:
+ $expected = Http::delete($path);
break;
}
$_SERVER['REQUEST_METHOD'] = $method;
$_SERVER['REQUEST_URI'] = $url;
- $this->assertEquals($expected, $this->app->match(new Request()));
- $this->assertEquals($expected, $this->app->getRoute());
+ $this->assertEquals($expected, $this->http->match(new Request()));
+ $this->assertEquals($expected, $this->http->getRoute());
}
public function testNoMismatchRoute(): void
@@ -470,43 +441,43 @@ public function testNoMismatchRoute(): void
];
foreach ($requests as $request) {
- App::get($request['path']);
+ Http::get($request['path']);
- $_SERVER['REQUEST_METHOD'] = App::REQUEST_METHOD_GET;
+ $_SERVER['REQUEST_METHOD'] = Http::REQUEST_METHOD_GET;
$_SERVER['REQUEST_URI'] = $request['url'];
- $route = $this->app->match(new Request(), fresh: true);
+ $route = $this->http->match(new Request(), fresh: true);
$this->assertEquals(null, $route);
- $this->assertEquals(null, $this->app->getRoute());
+ $this->assertEquals(null, $this->http->getRoute());
}
}
public function testCanMatchFreshRoute(): void
{
- $route1 = App::get('/path1');
- $route2 = App::get('/path2');
+ $route1 = Http::get('/path1');
+ $route2 = Http::get('/path2');
try {
// Match first request
$_SERVER['REQUEST_METHOD'] = 'HEAD';
$_SERVER['REQUEST_URI'] = '/path1';
- $matched = $this->app->match(new Request());
+ $matched = $this->http->match(new Request());
$this->assertEquals($route1, $matched);
- $this->assertEquals($route1, $this->app->getRoute());
+ $this->assertEquals($route1, $this->http->getRoute());
// Second request match returns cached route
$_SERVER['REQUEST_METHOD'] = 'HEAD';
$_SERVER['REQUEST_URI'] = '/path2';
$request2 = new Request();
- $matched = $this->app->match($request2);
+ $matched = $this->http->match($request2, fresh: false);
$this->assertEquals($route1, $matched);
- $this->assertEquals($route1, $this->app->getRoute());
+ $this->assertEquals($route1, $this->http->getRoute());
// Fresh match returns new route
- $matched = $this->app->match($request2, fresh: true);
+ $matched = $this->http->match($request2, fresh: true);
$this->assertEquals($route2, $matched);
- $this->assertEquals($route2, $this->app->getRoute());
+ $this->assertEquals($route2, $this->http->getRoute());
} catch (\Exception $e) {
$this->fail($e->getMessage());
}
@@ -522,14 +493,14 @@ public function testCanRunRequest(): void
$_SERVER['REQUEST_METHOD'] = 'HEAD';
$_SERVER['REQUEST_URI'] = '/path';
- App::get('/path')
+ Http::get('/path')
->inject('response')
->action(function ($response) {
$response->send('HELLO');
});
\ob_start();
- $this->app->run(new Request(), new Response());
+ $this->http->run(new Request(), new Response());
$result = \ob_get_contents();
\ob_end_clean();
@@ -547,34 +518,14 @@ public function testWildcardRoute(): void
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/unknown_path';
- App::init()
- ->inject('request')
- ->inject('response')
- ->action(function (Request $request, Response $response) {
- $route = $this->app->getRoute();
- App::setResource('myRoute', fn () => $route);
-
- if ($request->getURI() === '/init_response') {
- $response->send('THIS IS RESPONSE FROM INIT!');
- }
+ Http::init()
+ ->action(function () {
+ $route = $this->http->getRoute();
+ $this->container->set('myRoute', fn () => $route);
});
- App::options()
- ->inject('request')
- ->inject('response')
- ->action(function (Request $request, Response $response) {
- $origin = $request->getOrigin();
- $response
- ->addHeader('Server', 'Appwrite')
- ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
- ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
- ->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
- ->addHeader('Access-Control-Allow-Origin', $origin)
- ->addHeader('Access-Control-Allow-Credentials', 'true')
- ->noContent();
- });
- App::wildcard()
+ Http::wildcard()
->inject('myRoute')
->inject('response')
->action(function (mixed $myRoute, $response) {
@@ -586,7 +537,7 @@ public function testWildcardRoute(): void
});
\ob_start();
- @$this->app->run(new Request(), new Response());
+ @$this->http->run(new Request(), new Response());
$result = \ob_get_contents();
\ob_end_clean();
@@ -595,23 +546,70 @@ public function testWildcardRoute(): void
\ob_start();
$req = new Request();
$req = $req->setMethod('OPTIONS');
- @$this->app->run($req, new Response());
+ @$this->http->run($req, new Response());
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals('', $result);
- $_SERVER['REQUEST_METHOD'] = 'GET';
- $_SERVER['REQUEST_URI'] = '/init_response';
+ $_SERVER['REQUEST_METHOD'] = $method;
+ $_SERVER['REQUEST_URI'] = $uri;
+ }
+
+ public function testCallableStringParametersNotExecuted(): void
+ {
+ // Test that callable strings (like function names) are not executed
+ $route = new Route('GET', '/test-callable-string');
+
+ $route
+ ->param('callback', 'phpinfo', new Text(200), 'callback param', true)
+ ->action(function ($callback) {
+ // If the string 'phpinfo' was executed as a function,
+ // it would output PHP info. Instead, it should just be the string.
+ echo 'callback-value: ' . $callback;
+ });
\ob_start();
- @$this->app->run(new Request(), new Response());
+ $this->http->execute($route, new Request());
$result = \ob_get_contents();
\ob_end_clean();
- $this->assertEquals('THIS IS RESPONSE FROM INIT!', $result);
+ $this->assertEquals('callback-value: phpinfo', $result);
- $_SERVER['REQUEST_METHOD'] = $method;
- $_SERVER['REQUEST_URI'] = $uri;
+ // Test with request parameter that is a callable string
+ $route2 = new Route('GET', '/test-callable-string-param');
+
+ $route2
+ ->param('func', 'default', new Text(200), 'func param', false)
+ ->action(function ($func) {
+ echo 'func-value: ' . $func;
+ });
+
+ \ob_start();
+ $request = new UtopiaFPMRequestTest();
+ $request::_setParams(['func' => 'system']);
+ $this->http->execute($route2, $request);
+ $result = \ob_get_contents();
+ \ob_end_clean();
+
+ $this->assertEquals('func-value: system', $result);
+
+ // Test callable closure still works
+ $route3 = new Route('GET', '/test-callable-closure');
+
+ $route3
+ ->param('generated', function () {
+ return 'generated-value';
+ }, new Text(200), 'generated param', true)
+ ->action(function ($generated) {
+ echo 'generated: ' . $generated;
+ });
+
+ \ob_start();
+ $this->http->execute($route3, new Request());
+ $result = \ob_get_contents();
+ \ob_end_clean();
+
+ $this->assertEquals('generated: generated-value', $result);
}
}
diff --git a/tests/RequestTest.php b/tests/RequestTest.php
index 727d534a..02d8930a 100755
--- a/tests/RequestTest.php
+++ b/tests/RequestTest.php
@@ -1,9 +1,9 @@
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/ResponseTest.php b/tests/ResponseTest.php
index 30b20477..8dd8394a 100755
--- a/tests/ResponseTest.php
+++ b/tests/ResponseTest.php
@@ -1,10 +1,11 @@
response->setContentType(Response::CONTENT_TYPE_HTML, Response::CHARSET_UTF8);
// Assertions
- $this->assertInstanceOf('Utopia\Response', $contentType);
+ $this->assertInstanceOf('Utopia\Http\Response', $contentType);
}
public function testCanSetStatus()
@@ -31,11 +32,11 @@ public function testCanSetStatus()
$status = $this->response->setStatusCode(Response::STATUS_CODE_OK);
// Assertions
- $this->assertInstanceOf('Utopia\Response', $status);
+ $this->assertInstanceOf('Utopia\Http\Response', $status);
try {
$this->response->setStatusCode(0); // Unknown status code
- } catch(\Exception $e) {
+ } catch (\Exception $e) {
$this->assertInstanceOf('\Exception', $e);
return;
@@ -49,7 +50,7 @@ public function testCanGetStatus()
$status = $this->response->setStatusCode(Response::STATUS_CODE_OK);
// Assertions
- $this->assertInstanceOf('Utopia\Response', $status);
+ $this->assertInstanceOf('Utopia\Http\Response', $status);
$this->assertEquals(Response::STATUS_CODE_OK, $this->response->getStatusCode());
}
diff --git a/tests/RouteTest.php b/tests/RouteTest.php
index cbdea1c3..ea48e111 100755
--- a/tests/RouteTest.php
+++ b/tests/RouteTest.php
@@ -1,6 +1,6 @@
assertEquals($routeIndex, Router::match(App::REQUEST_METHOD_GET, '/'));
- $this->assertEquals($routeAbout, Router::match(App::REQUEST_METHOD_GET, '/about'));
- $this->assertEquals($routeAboutMe, Router::match(App::REQUEST_METHOD_GET, '/about/me'));
+ $this->assertEquals($routeIndex, Router::match(Http::REQUEST_METHOD_GET, '/'));
+ $this->assertEquals($routeAbout, Router::match(Http::REQUEST_METHOD_GET, '/about'));
+ $this->assertEquals($routeAboutMe, Router::match(Http::REQUEST_METHOD_GET, '/about/me'));
}
public function testCanMatchUrlWithPlaceholder(): void
{
- $routeBlog = new Route(App::REQUEST_METHOD_GET, '/blog');
- $routeBlogAuthors = new Route(App::REQUEST_METHOD_GET, '/blog/authors');
- $routeBlogAuthorsComments = new Route(App::REQUEST_METHOD_GET, '/blog/authors/comments');
- $routeBlogPost = new Route(App::REQUEST_METHOD_GET, '/blog/:post');
- $routeBlogPostComments = new Route(App::REQUEST_METHOD_GET, '/blog/:post/comments');
- $routeBlogPostCommentsSingle = new Route(App::REQUEST_METHOD_GET, '/blog/:post/comments/:comment');
+ $routeBlog = new Route(Http::REQUEST_METHOD_GET, '/blog');
+ $routeBlogAuthors = new Route(Http::REQUEST_METHOD_GET, '/blog/authors');
+ $routeBlogAuthorsComments = new Route(Http::REQUEST_METHOD_GET, '/blog/authors/comments');
+ $routeBlogPost = new Route(Http::REQUEST_METHOD_GET, '/blog/:post');
+ $routeBlogPostComments = new Route(Http::REQUEST_METHOD_GET, '/blog/:post/comments');
+ $routeBlogPostCommentsSingle = new Route(Http::REQUEST_METHOD_GET, '/blog/:post/comments/:comment');
Router::addRoute($routeBlog);
Router::addRoute($routeBlogAuthors);
@@ -42,12 +42,12 @@ public function testCanMatchUrlWithPlaceholder(): void
Router::addRoute($routeBlogPostComments);
Router::addRoute($routeBlogPostCommentsSingle);
- $this->assertEquals($routeBlog, Router::match(App::REQUEST_METHOD_GET, '/blog'));
- $this->assertEquals($routeBlogAuthors, Router::match(App::REQUEST_METHOD_GET, '/blog/authors'));
- $this->assertEquals($routeBlogAuthorsComments, Router::match(App::REQUEST_METHOD_GET, '/blog/authors/comments'));
- $this->assertEquals($routeBlogPost, Router::match(App::REQUEST_METHOD_GET, '/blog/test'));
- $this->assertEquals($routeBlogPostComments, Router::match(App::REQUEST_METHOD_GET, '/blog/test/comments'));
- $this->assertEquals($routeBlogPostCommentsSingle, Router::match(App::REQUEST_METHOD_GET, '/blog/test/comments/:comment'));
+ $this->assertEquals($routeBlog, Router::match(Http::REQUEST_METHOD_GET, '/blog'));
+ $this->assertEquals($routeBlogAuthors, Router::match(Http::REQUEST_METHOD_GET, '/blog/authors'));
+ $this->assertEquals($routeBlogAuthorsComments, Router::match(Http::REQUEST_METHOD_GET, '/blog/authors/comments'));
+ $this->assertEquals($routeBlogPost, Router::match(Http::REQUEST_METHOD_GET, '/blog/test'));
+ $this->assertEquals($routeBlogPostComments, Router::match(Http::REQUEST_METHOD_GET, '/blog/test/comments'));
+ $this->assertEquals($routeBlogPostCommentsSingle, Router::match(Http::REQUEST_METHOD_GET, '/blog/test/comments/:comment'));
}
public function testCanMatchUrlWithWildcard(): void
@@ -69,36 +69,36 @@ public function testCanMatchUrlWithWildcard(): void
public function testCanMatchHttpMethod(): void
{
- $routeGET = new Route(App::REQUEST_METHOD_GET, '/');
- $routePOST = new Route(App::REQUEST_METHOD_POST, '/');
+ $routeGET = new Route(Http::REQUEST_METHOD_GET, '/');
+ $routePOST = new Route(Http::REQUEST_METHOD_POST, '/');
Router::addRoute($routeGET);
Router::addRoute($routePOST);
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/'));
- $this->assertEquals($routePOST, Router::match(App::REQUEST_METHOD_POST, '/'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/'));
+ $this->assertEquals($routePOST, Router::match(Http::REQUEST_METHOD_POST, '/'));
- $this->assertNotEquals($routeGET, Router::match(App::REQUEST_METHOD_POST, '/'));
- $this->assertNotEquals($routePOST, Router::match(App::REQUEST_METHOD_GET, '/'));
+ $this->assertNotEquals($routeGET, Router::match(Http::REQUEST_METHOD_POST, '/'));
+ $this->assertNotEquals($routePOST, Router::match(Http::REQUEST_METHOD_GET, '/'));
}
public function testCanMatchAlias(): void
{
- $routeGET = new Route(App::REQUEST_METHOD_GET, '/target');
+ $routeGET = new Route(Http::REQUEST_METHOD_GET, '/target');
$routeGET
->alias('/alias')
->alias('/alias2');
Router::addRoute($routeGET);
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/target'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/alias'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/alias2'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/target'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/alias'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/alias2'));
}
public function testCanMatchMix(): void
{
- $routeGET = new Route(App::REQUEST_METHOD_GET, '/');
+ $routeGET = new Route(Http::REQUEST_METHOD_GET, '/');
$routeGET
->alias('/console/*')
->alias('/auth/*')
@@ -109,37 +109,37 @@ public function testCanMatchMix(): void
Router::addRoute($routeGET);
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/console'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/invite'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/login'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/recover'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/console/lorem/ipsum/dolor'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/auth/lorem/ipsum'));
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/register/lorem/ipsum'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/console'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/invite'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/login'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/recover'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/console/lorem/ipsum/dolor'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/auth/lorem/ipsum'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/register/lorem/ipsum'));
}
public function testCanMatchFilename(): void
{
- $routeGET = new Route(App::REQUEST_METHOD_GET, '/robots.txt');
+ $routeGET = new Route(Http::REQUEST_METHOD_GET, '/robots.txt');
Router::addRoute($routeGET);
- $this->assertEquals($routeGET, Router::match(App::REQUEST_METHOD_GET, '/robots.txt'));
+ $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/robots.txt'));
}
public function testCannotFindUnknownRouteByPath(): void
{
- $this->assertNull(Router::match(App::REQUEST_METHOD_GET, '/404'));
+ $this->assertNull(Router::match(Http::REQUEST_METHOD_GET, '/404'));
}
public function testCannotFindUnknownRouteByMethod(): void
{
- $route = new Route(App::REQUEST_METHOD_GET, '/404');
+ $route = new Route(Http::REQUEST_METHOD_GET, '/404');
Router::addRoute($route);
- $this->assertEquals($route, Router::match(App::REQUEST_METHOD_GET, '/404'));
+ $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/404'));
- $this->assertNull(Router::match(App::REQUEST_METHOD_POST, '/404'));
+ $this->assertNull(Router::match(Http::REQUEST_METHOD_POST, '/404'));
}
}
diff --git a/tests/UtopiaRequestTest.php b/tests/UtopiaFPMRequestTest.php
similarity index 90%
rename from tests/UtopiaRequestTest.php
rename to tests/UtopiaFPMRequestTest.php
index 19ae0f6b..4c78751d 100644
--- a/tests/UtopiaRequestTest.php
+++ b/tests/UtopiaFPMRequestTest.php
@@ -1,10 +1,10 @@
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\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\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\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 8bf1c96e..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\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 b1742918..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\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\Validator::TYPE_BOOLEAN, $boolean->getType());
- }
-}
diff --git a/tests/Validator/DomainTest.php b/tests/Validator/DomainTest.php
deleted file mode 100644
index ca14a20e..00000000
--- a/tests/Validator/DomainTest.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
- * @version 1.0 RC4
- * @license The MIT License (MIT)
- */
-
-namespace Utopia\Validator;
-
-use PHPUnit\Framework\TestCase;
-
-class DomainTest extends TestCase
-{
- protected Domain $domain;
-
- public function setUp(): void
- {
- $this->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 80d8f4dd..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\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\Validator::TYPE_FLOAT, $validator->getType());
- }
-}
diff --git a/tests/Validator/HexColorTest.php b/tests/Validator/HexColorTest.php
deleted file mode 100755
index 93b7aa64..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\Validator::TYPE_STRING, $hexColor->getType());
- }
-}
diff --git a/tests/Validator/HostTest.php b/tests/Validator/HostTest.php
deleted file mode 100644
index 6ad38299..00000000
--- a/tests/Validator/HostTest.php
+++ /dev/null
@@ -1,37 +0,0 @@
-
- * @version 1.0 RC4
- * @license The MIT License (MIT)
- */
-
-namespace Utopia\Validator;
-
-use PHPUnit\Framework\TestCase;
-
-class HostTest extends TestCase
-{
- protected Host $host;
-
- public function setUp(): void
- {
- $this->host = new Host(['example.io', 'subdomain.example.test', 'localhost']);
- }
-
- 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->getType(), 'string');
- }
-}
diff --git a/tests/Validator/HostnameTest.php b/tests/Validator/HostnameTest.php
deleted file mode 100755
index a1e492e0..00000000
--- a/tests/Validator/HostnameTest.php
+++ /dev/null
@@ -1,106 +0,0 @@
-assertEquals(\Utopia\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 124be488..00000000
--- a/tests/Validator/IPTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-
- * @version 1.0 RC4
- * @license The MIT License (MIT)
- */
-
-namespace Utopia\Validator;
-
-use PHPUnit\Framework\TestCase;
-
-class IPTest extends TestCase
-{
- protected IP $validator;
-
- public function testIsValidIP()
- {
- $validator = new IP();
-
- // 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);
- $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 4161b3ad..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\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\Validator::TYPE_INTEGER, $validator->getType());
- }
-}
diff --git a/tests/Validator/JSONTest.php b/tests/Validator/JSONTest.php
deleted file mode 100755
index bafce37c..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\Validator::TYPE_OBJECT, $json->getType());
- }
-}
diff --git a/tests/Validator/MultipleTest.php b/tests/Validator/MultipleTest.php
deleted file mode 100644
index 8ef1ee59..00000000
--- a/tests/Validator/MultipleTest.php
+++ /dev/null
@@ -1,35 +0,0 @@
-validator = new Multiple([new Text(20), new URL()], Multiple::TYPE_STRING);
- }
-
- public function testIsValid()
- {
- $this->assertEquals('string', $this->validator->getType());
- $this->assertEquals("1. Value must be a valid string and at least 1 chars and no longer than 20 chars \n2. Value must be a valid URL \n", $this->validator->getDescription());
-
- // Valid URL but invalid text length
- $this->assertFalse($this->validator->isValid('http://example.com/very-long-url'));
-
- // Valid text within length, but invalid URL
- $this->assertFalse($this->validator->isValid('hello world'));
-
- // Both conditions satisfied
- $this->assertTrue($this->validator->isValid('http://example.com'));
- $this->assertTrue($this->validator->isValid('https://google.com'));
-
- // Neither condition satisfied
- $this->assertFalse($this->validator->isValid('example.com/hello-world'));
- $this->assertFalse($this->validator->isValid(''));
- }
-}
diff --git a/tests/Validator/NullableTest.php b/tests/Validator/NullableTest.php
deleted file mode 100755
index fa15c530..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 d90be6b3..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\Validator::TYPE_MIXED, $numeric->getType());
- }
-}
diff --git a/tests/Validator/RangeTest.php b/tests/Validator/RangeTest.php
deleted file mode 100755
index 2bd1ee68..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\Validator::TYPE_INTEGER, $range->getFormat());
- $this->assertEquals(\Utopia\Validator::TYPE_INTEGER, $range->getType());
- }
-
- public function testCanValidateFloatRange()
- {
- $range = new Range(0, 1, \Utopia\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\Validator::TYPE_FLOAT, $range->getFormat());
- $this->assertEquals(\Utopia\Validator::TYPE_FLOAT, $range->getType(), \Utopia\Validator::TYPE_FLOAT);
- }
-
- public function canValidateInfinityRange()
- {
- $integer = new Range(5, INF, \Utopia\Validator::TYPE_INTEGER);
- $float = new Range(-INF, 45.6, \Utopia\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 cb987043..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\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 e7bad1b5..00000000
--- a/tests/Validator/URLTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-
- * @version 1.0 RC4
- * @license The MIT License (MIT)
- */
-
-namespace Utopia\Validator;
-
-use PHPUnit\Framework\TestCase;
-
-class URLTest extends TestCase
-{
- protected ?URL $url;
-
- public function setUp(): void
- {
- $this->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 9d88f75e..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\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 b79c3580..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\Validator::TYPE_STRING, $validator->getType());
- }
-}
diff --git a/tests/ViewTest.php b/tests/ViewTest.php
deleted file mode 100755
index e21131af..00000000
--- a/tests/ViewTest.php
+++ /dev/null
@@ -1,92 +0,0 @@
-view = new View(__DIR__.'/mocks/View/template.phtml');
- }
-
- public function tearDown(): void
- {
- $this->view = null;
- }
-
- public function testCanSetParam()
- {
- $value = $this->view->setParam('key', 'value');
-
- $this->assertInstanceOf('Utopia\View', $value);
- }
-
- public function testCanGetParam()
- {
- $this->view->setParam('key', 'value');
-
- $this->assertEquals('value', $this->view->getParam('key', 'default'));
- $this->assertEquals('default', $this->view->getParam('fake', 'default'));
- }
-
- public function testCanSetPath()
- {
- $value = $this->view->setPath('mocks/View/fake.phtml');
-
- $this->assertInstanceOf('Utopia\View', $value);
- }
-
- public function testCanSetRendered()
- {
- $this->view->setRendered();
-
- $this->assertEquals(true, $this->view->isRendered());
- }
-
- public function testCanGetRendered()
- {
- $this->view->setRendered(false);
- $this->assertEquals(false, $this->view->isRendered());
-
- $this->view->setRendered(true);
- $this->assertEquals(true, $this->view->isRendered());
- }
-
- public function testCanRenderHtml()
- {
- $this->assertEquals('Test template mock
', $this->view->render());
-
- $this->view->setRendered();
- $this->assertEquals('', $this->view->render());
-
- try {
- $this->view->setRendered(false);
- $this->view->setPath('just-a-broken-string.phtml');
- $this->view->render();
- } catch(\Exception $e) {
- return;
- }
-
- $this->fail('An expected exception has not been raised.');
- }
-
- public function testCanEscapeUnicode()
- {
- $this->assertEquals('&"', $this->view->print('&"', View::FILTER_ESCAPE));
- }
-
- public function testCanFilterNewLinesToParagraphs()
- {
- $this->assertEquals('line1
line2
', $this->view->print("line1\n\nline2", View::FILTER_NL2P));
- }
-
- public function testCanSetParamWithEscapedHtml()
- {
- $this->view->setParam('key', 'value');
- $this->assertEquals('<html>value</html>', $this->view->getParam('key', 'default'));
- }
-}
diff --git a/tests/docker/nginx.conf b/tests/docker/nginx.conf
index 978f1c03..f38e0e60 100644
--- a/tests/docker/nginx.conf
+++ b/tests/docker/nginx.conf
@@ -33,7 +33,7 @@ http {
listen [::]:80 ipv6only=on; ## listen for ipv6
root /usr/share/nginx/html/tests/e2e;
- index index.php server.php index.html index.htm;
+ index index.php server-fpm.php index.html index.htm;
server_tokens off;
@@ -53,7 +53,7 @@ http {
location / {
# First attempt to serve request as file, then
# as directory, then fall back to index.html
- try_files $uri $uri/ /server.php?q=$uri&$args;
+ try_files $uri $uri/ /server-fpm.php?q=$uri&$args;
}
@@ -74,7 +74,7 @@ http {
fastcgi_param HTTP_IF_NONE_MATCH $http_if_none_match;
fastcgi_param HTTP_IF_MODIFIED_SINCE $http_if_modified_since;
fastcgi_read_timeout 600;
- fastcgi_index server.php;
+ fastcgi_index server-fpm.php;
include fastcgi_params;
}
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/BaseTest.php b/tests/e2e/BaseTest.php
new file mode 100644
index 00000000..04b4249d
--- /dev/null
+++ b/tests/e2e/BaseTest.php
@@ -0,0 +1,79 @@
+client->call(Client::METHOD_GET, '/');
+ $this->assertEquals('Hello World!', $response['body']);
+ }
+
+ public function testResponseValue()
+ {
+ $response = $this->client->call(Client::METHOD_GET, '/value/123');
+ $this->assertEquals('123', $response['body']);
+ }
+
+ public function testChunkResponse()
+ {
+ $response = $this->client->call(Client::METHOD_GET, '/chunked');
+ $this->assertEquals('Hello World!', $response['body']);
+ }
+
+ public function testRedirect()
+ {
+ $response = $this->client->call(Client::METHOD_GET, '/redirect');
+ $this->assertEquals('Hello World!', $response['body']);
+ }
+
+ public function testFile()
+ {
+ $response = $this->client->call(Client::METHOD_GET, '/humans.txt');
+ $this->assertEquals(204, $response['headers']['status-code']);
+ }
+
+ public function testCookie()
+ {
+ // One cookie
+ $cookie = 'cookie1=value1';
+ $response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($cookie, $response['body']);
+
+ // Two cookiees
+ $cookie = 'cookie1=value1; cookie2=value2';
+ $response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($cookie, $response['body']);
+
+ // Two cookies without optional space
+ $cookie = 'cookie1=value1;cookie2=value2';
+ $response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($cookie, $response['body']);
+
+ // Cookie with "=" in value
+ $cookie = 'cookie1=value1=value2';
+ $response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($cookie, $response['body']);
+
+ // Case sensitivity for cookie names
+ $cookie = 'cookie1=v1; Cookie1=v2';
+ $response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($cookie, $response['body']);
+ }
+
+ public function testSetCookie()
+ {
+ $response = $this->client->call(Client::METHOD_GET, '/set-cookie');
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals('value1', $response['cookies']['key1']);
+ $this->assertEquals('value2', $response['cookies']['key2']);
+ }
+}
diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php
index 4052689b..42fdd5a8 100644
--- a/tests/e2e/Client.php
+++ b/tests/e2e/Client.php
@@ -29,13 +29,14 @@ class Client
*
* @var string
*/
- protected $baseUrl = 'http://web';
+ protected $baseUrl;
/**
* SDK constructor.
*/
- public function __construct()
+ public function __construct(string $baseUrl = 'http://fpm')
{
+ $this->baseUrl = $baseUrl;
}
/**
@@ -60,6 +61,8 @@ public function call(string $method, string $path = '', array $headers = [], arr
$responseType = '';
$responseBody = '';
+ $cookies = [];
+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
@@ -67,7 +70,7 @@ public function call(string $method, string $path = '', array $headers = [], arr
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
- curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
+ curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders, &$cookies) {
$len = strlen($header);
$header = explode(':', $header, 2);
@@ -75,6 +78,12 @@ public function call(string $method, string $path = '', array $headers = [], arr
return $len;
}
+ if (strtolower(trim($header[0])) == 'set-cookie') {
+ $parsed = $this->parseCookie((string)trim($header[1]));
+ $name = array_key_first($parsed);
+ $cookies[$name] = $parsed[$name];
+ }
+
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
@@ -98,6 +107,23 @@ public function call(string $method, string $path = '', array $headers = [], arr
return [
'headers' => $responseHeaders,
'body' => $responseBody,
+ 'cookies' => $cookies,
];
}
+
+ /**
+ * Parse Cookie String
+ *
+ * @param string $cookie
+ * @return array
+ */
+ public function parseCookie(string $cookie): array
+ {
+ $cookies = [];
+
+ parse_str(strtr($cookie, ['&' => '%26', '+' => '%2B', ';' => '&']), $cookies);
+
+ return $cookies;
+ }
+
}
diff --git a/tests/e2e/ResponseFPMTest.php b/tests/e2e/ResponseFPMTest.php
new file mode 100644
index 00000000..8a3cb4ff
--- /dev/null
+++ b/tests/e2e/ResponseFPMTest.php
@@ -0,0 +1,17 @@
+client = new Client();
+ }
+}
diff --git a/tests/e2e/ResponseSwooleTest.php b/tests/e2e/ResponseSwooleTest.php
new file mode 100755
index 00000000..0851554a
--- /dev/null
+++ b/tests/e2e/ResponseSwooleTest.php
@@ -0,0 +1,23 @@
+client = new Client('http://swoole');
+ }
+
+ public function testSwooleResources(): void
+ {
+ $response = $this->client->call(Client::METHOD_DELETE, '/swoole-test');
+ $this->assertEquals('DELETE', $response['body']);
+ }
+}
diff --git a/tests/e2e/ResponseTest.php b/tests/e2e/ResponseTest.php
deleted file mode 100644
index 878cc23f..00000000
--- a/tests/e2e/ResponseTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-client = new Client();
- }
-
- public function testResponse()
- {
- $response = $this->client->call(Client::METHOD_GET, '/');
- $this->assertEquals('Hello World!', $response['body']);
- }
-
- public function testResponseValue()
- {
- $response = $this->client->call(Client::METHOD_GET, '/value/123');
- $this->assertEquals('123', $response['body']);
- }
-
- public function testChunkResponse()
- {
- $response = $this->client->call(Client::METHOD_GET, '/chunked');
- $this->assertEquals('Hello World!', $response['body']);
- }
-
- public function testRedirect()
- {
- $response = $this->client->call(Client::METHOD_GET, '/redirect');
- $this->assertEquals('Hello World!', $response['body']);
- }
-
- public function testFile()
- {
- $response = $this->client->call(Client::METHOD_GET, '/humans.txt');
- $this->assertEquals(204, $response['headers']['status-code']);
- }
-}
diff --git a/tests/e2e/server.php b/tests/e2e/init.php
similarity index 58%
rename from tests/e2e/server.php
rename to tests/e2e/init.php
index cef864ee..fb0d9d59 100644
--- a/tests/e2e/server.php
+++ b/tests/e2e/init.php
@@ -2,9 +2,9 @@
require_once __DIR__.'/../../vendor/autoload.php';
-use Utopia\App;
-use Utopia\Request;
-use Utopia\Response;
+use Utopia\Http\Http;
+use Utopia\Http\Request;
+use Utopia\Http\Response;
use Utopia\Validator\Text;
ini_set('memory_limit', '1024M');
@@ -13,20 +13,37 @@
ini_set('display_socket_timeout', '-1');
error_reporting(E_ALL);
-App::get('/')
+Http::get('/')
->inject('response')
->action(function (Response $response) {
$response->send('Hello World!');
});
-App::get('/value/:value')
+Http::get('/value/:value')
->param('value', '', new Text(64))
->inject('response')
->action(function (string $value, Response $response) {
$response->send($value);
});
-App::get('/chunked')
+
+Http::get('/cookies')
+ ->inject('request')
+ ->inject('response')
+ ->action(function (Request $request, Response $response) {
+ $response->send($request->getHeaders()['cookie'] ?? '');
+ });
+
+Http::get('/set-cookie')
+ ->inject('request')
+ ->inject('response')
+ ->action(function (Request $request, Response $response) {
+ $response->addHeader('Set-Cookie', 'key1=value1');
+ $response->addHeader('Set-Cookie', 'key2=value2');
+ $response->send('OK');
+ });
+
+Http::get('/chunked')
->inject('response')
->action(function (Response $response) {
foreach (['Hello ', 'World!'] as $key => $word) {
@@ -34,20 +51,14 @@
}
});
-App::get('/redirect')
+Http::get('/redirect')
->inject('response')
->action(function (Response $response) {
$response->redirect('/');
});
-App::get('/humans.txt')
+Http::get('/humans.txt')
->inject('response')
->action(function (Response $response) {
$response->noContent();
});
-
-$request = new Request();
-$response = new Response();
-
-$app = new App('UTC');
-$app->run($request, $response);
diff --git a/tests/e2e/server-fpm.php b/tests/e2e/server-fpm.php
new file mode 100644
index 00000000..eb15c7e2
--- /dev/null
+++ b/tests/e2e/server-fpm.php
@@ -0,0 +1,11 @@
+start();
diff --git a/tests/e2e/server-swoole.php b/tests/e2e/server-swoole.php
new file mode 100755
index 00000000..e15e1be4
--- /dev/null
+++ b/tests/e2e/server-swoole.php
@@ -0,0 +1,29 @@
+inject('swooleRequest')
+ ->inject('swooleResponse')
+ ->action(function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
+ $method = $swooleRequest->getMethod();
+ $swooleResponse->header('Content-Type', 'text/plain');
+ $swooleResponse->header('Cache-Control', 'no-cache');
+ $swooleResponse->setStatusCode(200);
+ $swooleResponse->write($method);
+ $swooleResponse->end();
+ });
+
+$server = new Server('0.0.0.0', '80');
+$http = new Http($server, 'UTC');
+
+run(function () use ($http) {
+ $http->start();
+});
diff --git a/tests/mocks/View/template.phtml b/tests/mocks/View/template.phtml
deleted file mode 100755
index 51d1c6a4..00000000
--- a/tests/mocks/View/template.phtml
+++ /dev/null
@@ -1 +0,0 @@
-Test template mock
\ No newline at end of file