diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cc41aa6f..ce9d61c5 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
- php-version: '8.0'
+ php-version: '8.1'
- name: Setup Docker
run: docker-compose up -d --build
@@ -21,5 +21,8 @@ jobs:
- 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
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7cc480d1..4701ba07 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,6 +64,12 @@ $ 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 🙏.
diff --git a/Dockerfile b/Dockerfile.fpm
similarity index 100%
rename from Dockerfile
rename to Dockerfile.fpm
diff --git a/Dockerfile.swoole b/Dockerfile.swoole
new file mode 100644
index 00000000..6e0fdba1
--- /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.4.3 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..fe37a60e 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
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 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 Framework is dependency-free. Any extra features, such as authentication or caching, will be available as standalone models in order to keep the framework core clean, light, and easy to learn.
## Getting Started
@@ -17,19 +17,21 @@ Install using composer:
composer require utopia-php/framework
```
-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\Http\Http;
+use Utopia\Http\Request;
+use Utopia\Http\Response;
+use Utopia\Http\Adapter\FPM\Server;
-App::get('/hello-world') // Define Route
+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 +40,222 @@ App::get('/hello-world') // Define Route
}
);
-App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
+Http::setMode(Http::MODE_TYPE_PRODUCTION);
+
+$http = new Http(new Server(), 'America/New_York');
+$http->start();
+```
-$app = new App('America/New_York');
-$request = new Request();
-$response = new Response();
+Run HTTP server:
-$app->run($request, $response);
+```bash
+php -S localhost:8000 src/server2.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. For instance, you could use the FPM server or the Swoole server.
+
+#### Use PHP FPM server
+
+```php
+use Utopia\Http\Http;
+use Utopia\Http\Response;
+use Utopia\Http\Adapter\FPM\Server;
+
+Http::get('/')
+ ->inject('response')
+ ->action(
+ function(Response $response) {
+ $response->send('Hello from PHP FPM');
+ }
+ );
-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.
+$http = new Http(new Server(), 'America/New_York');
+$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\Http\Http;
+use Utopia\Http\Request;
+use Utopia\Http\Response;
+use Utopia\Http\Adapter\Swoole\Server;
+
+Http::get('/')
+ ->inject('request')
+ ->inject('response')
+ ->action(
+ function(Request $request, Response $response) {
+ $response->send('Hello from Swoole');
+ }
+ );
+
+$http = new Http(new Server('0.0.0.0', '80'), 'America/New_York');
+$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);
+ });
+```
+
+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:
-use Utopia\App;
-use Utopia\Request;
-use Utopia\Response;
+- **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.
-App::init()
+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->addHeader('content-type', 'application/json');
+ ->action(function(Response $response) {
+ \var_dump('Responding with status code: ' . $response->getStatusCode());
});
-App::error()
+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');
}
);
+```
-App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
+Now you can define hooks that would apply only to specific groups. Remember, hooks can also be assigned to multiple groups.
-$app = new App('America/New_York');
-$request = new Request();
-$response = new Response();
+```php
+Http::init()
+ ->groups(['api'])
+ ->inject('request')
+ ->inject('response')
+ ->action(function(Request $request, Response $response) {
+ $apiKey = $request->getHeader('x-api-key', '');
-$app->run($request, $response);
+ 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.
+
+### Resources
+
+Resources allow you to prepare dependencies for requests such as database connection or the user who sent the request. A new instance of a resource is created for every request.
+
+Define a resource:
+
+```php
+Http::setResource('timing', function() {
+ return \microtime(true);
+});
+```
+
+Inject resource into endpoint action:
+
+```php
+Http::get('/')
+ ->inject('timing')
+ ->inject('response')
+ ->action(function(float $timing, Response $response) {
+ $response->send('Request Unix timestamp: ' . \strval($timing));
+ });
+```
+
+Inject resource into a hook:
+
+```php
+Http::shutdown()
+ ->inject('timing')
+ ->action(function(float $timing) {
+ $difference = \microtime(true) - $timing;
+ \var_dump("Request took: " . $difference . " 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 Framework architecture and features, 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.
## More from Utopia
-Our ecosystem support other thin PHP projects aiming to extend the core PHP Utopia framework.
+Our ecosystem supports 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.
+You can find all libraries in [GitHub Utopia organization](https://github.com/utopia-php).
## Contributing
@@ -133,12 +267,6 @@ You can refer to the [Contributing Guide](https://github.com/utopia-php/framewor
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..043758fa 100644
--- a/composer.json
+++ b/composer.json
@@ -11,7 +11,7 @@
"minimum-stability": "stable",
"autoload": {
"psr-4": {
- "Utopia\\": "src/"
+ "Utopia\\Http\\": "src/Http"
}
},
"scripts": {
@@ -22,11 +22,13 @@
"bench": "vendor/bin/phpbench run --report=benchmark"
},
"require": {
- "php": ">=8.0"
+ "php": ">=8.0",
+ "ext-swoole": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.5.25",
"laravel/pint": "^1.2",
+ "swoole/ide-helper": "4.8.3",
"phpstan/phpstan": "^1.10",
"phpbench/phpbench": "^1.2"
}
diff --git a/composer.lock b/composer.lock
index 9bcef239..a388d0e9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d41ea47cadd897ad4053410229874733",
+ "content-hash": "f27d5bb02390ce6b9a8268d4fffb1e87",
"packages": [],
"packages-dev": [
{
@@ -2386,18 +2386,60 @@
],
"time": "2023-12-18T13:03:25+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": "v7.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "cdce5c684b2f920bb1343deecdfba356ffad83d5"
+ "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/cdce5c684b2f920bb1343deecdfba356ffad83d5",
- "reference": "cdce5c684b2f920bb1343deecdfba356ffad83d5",
+ "url": "https://api.github.com/repos/symfony/console/zipball/f8587c4cdc5acad67af71c37db34ef03af91e59c",
+ "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c",
"shasum": ""
},
"require": {
@@ -2461,7 +2503,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.0.1"
+ "source": "https://github.com/symfony/console/tree/v7.0.2"
},
"funding": [
{
@@ -2477,7 +2519,7 @@
"type": "tidelift"
}
],
- "time": "2023-12-01T15:10:06+00:00"
+ "time": "2023-12-10T16:54:46+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -3072,16 +3114,16 @@
},
{
"name": "symfony/process",
- "version": "v7.0.0",
+ "version": "v7.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "13bdb1670c7f510494e04fcb2bfa29af63db9c0d"
+ "reference": "acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/13bdb1670c7f510494e04fcb2bfa29af63db9c0d",
- "reference": "13bdb1670c7f510494e04fcb2bfa29af63db9c0d",
+ "url": "https://api.github.com/repos/symfony/process/zipball/acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a",
+ "reference": "acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a",
"shasum": ""
},
"require": {
@@ -3113,7 +3155,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/v7.0.2"
},
"funding": [
{
@@ -3129,25 +3171,25 @@
"type": "tidelift"
}
],
- "time": "2023-11-20T16:43:42+00:00"
+ "time": "2023-12-24T09:15:37+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.4.0",
+ "version": "v3.4.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838"
+ "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0"
},
"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/fe07cbc8d837f60caf7018068e350cc5163681a0",
+ "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0",
"shasum": ""
},
"require": {
"php": ">=8.1",
- "psr/container": "^2.0"
+ "psr/container": "^1.1|^2.0"
},
"conflict": {
"ext-psr": "<1.1|>=2"
@@ -3195,7 +3237,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.4.0"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.4.1"
},
"funding": [
{
@@ -3211,20 +3253,20 @@
"type": "tidelift"
}
],
- "time": "2023-07-30T20:28:31+00:00"
+ "time": "2023-12-26T14:02:43+00:00"
},
{
"name": "symfony/string",
- "version": "v7.0.0",
+ "version": "v7.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620"
+ "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
- "reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
+ "url": "https://api.github.com/repos/symfony/string/zipball/cc78f14f91f5e53b42044d0620961c48028ff9f5",
+ "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5",
"shasum": ""
},
"require": {
@@ -3281,7 +3323,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.0.0"
+ "source": "https://github.com/symfony/string/tree/v7.0.2"
},
"funding": [
{
@@ -3297,7 +3339,7 @@
"type": "tidelift"
}
],
- "time": "2023-11-29T08:40:23+00:00"
+ "time": "2023-12-10T16:54:46+00:00"
},
{
"name": "theseer/tokenizer",
@@ -3405,8 +3447,9 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=8.0"
+ "php": ">=8.0",
+ "ext-swoole": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.3.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 849fa5ec..0c4e0011 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,10 +1,29 @@
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..272a6b4a 100644
--- a/docs/Getting-Starting-Guide.md
+++ b/docs/Getting-Starting-Guide.md
@@ -9,16 +9,16 @@ If you’re new to Utopia, let’s get started by looking at an example of a bas
## Basic GET Route
```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;
$http = new Server("0.0.0.0", 8080);
-App::get('/')
+Http::get('/')
->inject('request')
->inject('response')
->action(
@@ -27,14 +27,14 @@ App::get('/')
$response->send("
Hello World!
");
}
/*
- Configure your HTTP server to respond with the Utopia app.
+ Configure your HTTP server to respond with the Utopia http.
*/
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
- $app = new App('America/Toronto');
- $app->run($request, $response);
+ $http = new Http('America/Toronto');
+ $http->run($request, $response);
});
$http->start();
@@ -59,14 +59,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){
@@ -138,38 +138,38 @@ 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;
-use Utopia\Validator\Wildcard;
+use Utopia\Http\Validator\Wildcard;
$http = new Server("0.0.0.0", 8080);
-App::init(function($response) {
+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) {
+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.
@@ -178,7 +178,7 @@ 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..
@@ -186,7 +186,7 @@ App::shutdown(function($request) {
}, ['request'], 'group1');
-App::put('/todos/:id')
+Http::put('/todos/:id')
->desc('Update todo')
->groups(['group1', 'group2'])
->label('scope', 'public')
@@ -197,7 +197,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){
@@ -227,7 +227,7 @@ For each endpoint, you can add the following properties described below. Let’s
`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.
@@ -239,7 +239,7 @@ Now that you’re familiar with routing in Utopia, let’s dive into the lifecyc
## 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:

@@ -255,7 +255,7 @@ The init and shutdown methods take three params:
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) {
+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..
@@ -268,7 +268,7 @@ 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) {
+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..
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..3abcbeed
--- /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.4.3 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..999df3c0
--- /dev/null
+++ b/example/composer.json
@@ -0,0 +1,6 @@
+{
+ "name": "utopia-php/http-app",
+ "require": {
+ "utopia-php/framework": "0.33.*"
+ }
+}
\ 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..4eb07f71
--- /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/src/Http/Adapter.php b/src/Http/Adapter.php
new file mode 100755
index 00000000..082ca6ab
--- /dev/null
+++ b/src/Http/Adapter.php
@@ -0,0 +1,10 @@
+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
+ {
+ return (string) \parse_url($this->getProtocol().'://'.$this->getServer('HTTP_HOST', ''), PHP_URL_HOST);
+ }
+
+ /**
+ * 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..88c47716
--- /dev/null
+++ b/src/Http/Adapter/FPM/Response.php
@@ -0,0 +1,82 @@
+ $request);
+ Http::setResource('fpmResponse', fn () => $response);
+
+ call_user_func($callback, $request, $response, 'fpm');
+ }
+
+ public function onStart(callable $callback)
+ {
+ call_user_func($callback, $this);
+ }
+
+ 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..8a717730
--- /dev/null
+++ b/src/Http/Adapter/Swoole/Request.php
@@ -0,0 +1,364 @@
+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
+ {
+ return strval(\parse_url($this->getProtocol().'://'.$this->getHeader('x-forwarded-host', $this->getHeader('host')), PHP_URL_HOST));
+ }
+
+ /**
+ * 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);
+
+ return $this->swoole->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
+ {
+ 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
+ {
+ return $this->swoole->header;
+ }
+}
diff --git a/src/Http/Adapter/Swoole/Response.php b/src/Http/Adapter/Swoole/Response.php
new file mode 100644
index 00000000..d4ecdf91
--- /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 void
+ */
+ protected function write(string $content): void
+ {
+ $this->swoole->write($content);
+ }
+
+ /**
+ * End
+ *
+ * @param string|null $content
+ * @return void
+ */
+ protected 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 $value
+ * @return void
+ */
+ protected function sendHeader(string $key, string $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..9fe73e98
--- /dev/null
+++ b/src/Http/Adapter/Swoole/Server.php
@@ -0,0 +1,51 @@
+server = new SwooleServer($host, $port);
+ $this->server->set(\array_merge($settings, [
+ 'enable_coroutine' => true
+ ]));
+ }
+
+ public function onRequest(callable $callback)
+ {
+ $this->server->handle('/', function (SwooleRequest $request, SwooleResponse $response) use ($callback) {
+ $context = \strval(Coroutine::getCid());
+
+ Http::setResource('swooleRequest', fn () => $request, [], $context);
+ Http::setResource('swooleResponse', fn () => $response, [], $context);
+
+ call_user_func($callback, new Request($request), new Response($response), $context);
+ });
+ }
+
+ 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..2266da26 100644
--- a/src/Hook.php
+++ b/src/Http/Hook.php
@@ -1,6 +1,6 @@
null,
];
+ /**
+ * @var Files
+ */
+ protected Files $files;
+
/**
* @var array
*/
@@ -85,6 +90,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 +122,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;
}
/**
@@ -330,26 +357,32 @@ public static function setAllowOverride(bool $value): void
*
* @throws Exception
*/
- public function getResource(string $name, bool $fresh = false): mixed
+ public function getResource(string $name, string $context = 'utopia', bool $fresh = false): mixed
{
if ($name === 'utopia') {
return $this;
}
- if (!\array_key_exists($name, $this->resources) || $fresh || self::$resourcesCallbacks[$name]['reset']) {
- if (!\array_key_exists($name, self::$resourcesCallbacks)) {
+ $this->resources[$context] ??= [];
+
+ $resourcesCallback = &self::$resourcesCallbacks[$context] ?? [];
+ if(empty($resourcesCallback) || !\array_key_exists($name, $resourcesCallback)) {
+ $resourcesCallback = &self::$resourcesCallbacks['utopia'];
+ }
+
+ if (!\array_key_exists($name, $this->resources[$context]) || $fresh || ($resourcesCallback[$name]['reset'][$context] ?? true)) {
+ if (!\array_key_exists($name, $resourcesCallback)) {
throw new Exception('Failed to find resource: "' . $name . '"');
}
- $this->resources[$name] = \call_user_func_array(
- self::$resourcesCallbacks[$name]['callback'],
- $this->getResources(self::$resourcesCallbacks[$name]['injections'])
+ $this->resources[$context][$name] = \call_user_func_array(
+ $resourcesCallback[$name]['callback'],
+ $this->getResources($resourcesCallback[$name]['injections'], $context)
);
}
- self::$resourcesCallbacks[$name]['reset'] = false;
-
- return $this->resources[$name];
+ $resourcesCallback[$name]['reset'][$context] = false;
+ return $this->resources[$context][$name];
}
/**
@@ -358,12 +391,12 @@ public function getResource(string $name, bool $fresh = false): mixed
* @param array $list
* @return array
*/
- public function getResources(array $list): array
+ public function getResources(array $list, string $context = 'utopia'): array
{
$resources = [];
foreach ($list as $name) {
- $resources[$name] = $this->getResource($name);
+ $resources[$name] = $this->getResource($name, $context);
}
return $resources;
@@ -379,16 +412,19 @@ public function getResources(array $list): array
*
* @throws Exception
*/
- public static function setResource(string $name, callable $callback, array $injections = []): void
+ public static function setResource(string $name, callable $callback, array $injections = [], string $context = 'utopia'): void
{
if ($name === 'utopia') {
throw new Exception("'utopia' is a reserved keyword.", 500);
}
- self::$resourcesCallbacks[$name] = ['callback' => $callback, 'injections' => $injections, 'reset' => true];
+
+ self::$resourcesCallbacks[$context] ??= [];
+
+ self::$resourcesCallbacks[$context][$name] = ['callback' => $callback, 'injections' => $injections, 'resets' => []];
}
/**
- * Is app in production mode?
+ * Is http in production mode?
*
* @return bool
*/
@@ -398,7 +434,7 @@ public static function isProduction(): bool
}
/**
- * Is app in development mode?
+ * Is http in development mode?
*
* @return bool
*/
@@ -408,7 +444,7 @@ public static function isDevelopment(): bool
}
/**
- * Is app in stage mode?
+ * Is http in stage mode?
*
* @return bool
*/
@@ -469,6 +505,113 @@ 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(function ($request, $response, $context) {
+ try {
+ $this->run($request, $response, $context);
+ } finally {
+ if(isset(self::$resourcesCallbacks[$context])) {
+ unset(self::$resourcesCallbacks[$context]);
+ }
+ }
+ });
+ $this->server->onStart(function ($server) {
+ $this->resources['utopia'] ??= [];
+ $this->resources['utopia']['server'] = $server;
+ self::setResource('server', function () use ($server) {
+ return $server;
+ });
+ try {
+
+ foreach (self::$startHooks as $hook) {
+ $arguments = $this->getArguments($hook, 'utopia', [], []);
+ \call_user_func_array($hook->getAction(), $arguments);
+ }
+ } catch(\Exception $e) {
+ self::setResource('error', fn () => $e);
+
+ foreach (self::$errors as $error) { // Global error hooks
+ if (in_array('*', $error->getGroups())) {
+ try {
+ $arguments = $this->getArguments($error, 'utopia', [], []);
+ \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 +621,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 +642,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, string $context): static
{
$arguments = [];
$groups = $route->getGroups();
@@ -509,7 +652,7 @@ public function execute(Route $route, Request $request, Response $response): sta
if ($route->getHook()) {
foreach (self::$init as $hook) { // Global init hooks
if (in_array('*', $hook->getGroups())) {
- $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($hook, $context, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
@@ -518,24 +661,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())) {
- $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($hook, $context, $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, $context, $pathValues, $request->getParams());
+ \call_user_func_array($route->getAction(), $arguments);
foreach ($groups as $group) {
foreach (self::$shutdown as $hook) { // Group shutdown hooks
if (in_array($group, $hook->getGroups())) {
- $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($hook, $context, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
@@ -544,19 +682,19 @@ 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())) {
- $arguments = $this->getArguments($hook, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($hook, $context, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}
} catch (\Throwable $e) {
- self::setResource('error', fn () => $e);
+ self::setResource('error', fn () => $e, [], $context);
foreach ($groups as $group) {
foreach (self::$errors as $error) { // Group error hooks
if (in_array($group, $error->getGroups())) {
try {
- $arguments = $this->getArguments($error, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($error, $context, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
@@ -568,7 +706,7 @@ public function execute(Route $route, Request $request, Response $response): sta
foreach (self::$errors as $error) { // Global error hooks
if (in_array('*', $error->getGroups())) {
try {
- $arguments = $this->getArguments($error, $pathValues, $request->getParams());
+ $arguments = $this->getArguments($error, $context, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
@@ -577,6 +715,9 @@ public function execute(Route $route, Request $request, Response $response): sta
}
}
+ // Reset resources for the context
+ $this->resources[$context] = [];
+
return $this;
}
@@ -590,7 +731,7 @@ public function execute(Route $route, Request $request, Response $response): sta
*
* @throws Exception
*/
- protected function getArguments(Hook $hook, array $values, array $requestParams): array
+ protected function getArguments(Hook $hook, string $context, array $values, array $requestParams): array
{
$arguments = [];
foreach ($hook->getParams() as $key => $param) { // Get value from route or request object
@@ -607,7 +748,7 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
}
if ($paramExists) {
- $this->validate($key, $param, $value);
+ $this->validate($key, $param, $value, $context);
}
}
@@ -616,7 +757,7 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
}
foreach ($hook->getInjections() as $key => $injection) {
- $arguments[$injection['order']] = $this->getResource($injection['name']);
+ $arguments[$injection['order']] = $this->getResource($injection['name'], $context);
}
return $arguments;
@@ -628,26 +769,59 @@ 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
+ public function run(Request $request, Response $response, string $context): static
{
- $this->resources['request'] = $request;
- $this->resources['response'] = $response;
+ $this->resources[$context] = [];
+ $this->resources[$context]['request'] = $request;
+ $this->resources[$context]['response'] = $response;
- self::setResource('request', function () use ($request) {
- return $request;
- });
+ self::setResource('context', fn () => $context, [], $context);
- self::setResource('response', function () use ($response) {
- return $response;
- });
+ self::setResource('request', fn () => $request, [], $context);
+
+ self::setResource('response', fn () => $response, [], $context);
+
+ try {
+
+ foreach (self::$requestHooks as $hook) {
+ $arguments = $this->getArguments($hook, $context, [], []);
+ \call_user_func_array($hook->getAction(), $arguments);
+ }
+ } catch(\Exception $e) {
+ self::setResource('error', fn () => $e, [], $context);
+
+ foreach (self::$errors as $error) { // Global error hooks
+ if (in_array('*', $error->getGroups())) {
+ try {
+ $arguments = $this->getArguments($error, $context, [], []);
+ \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() : [];
+ self::setResource('route', fn () => $route, [], $context);
+
if (self::REQUEST_METHOD_HEAD == $method) {
$method = self::REQUEST_METHOD_GET;
$response->disablePayload();
@@ -659,7 +833,7 @@ public function run(Request $request, Response $response): static
foreach (self::$options as $option) { // Group options hooks
/** @var Hook $option */
if (in_array($group, $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
+ \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
}
}
}
@@ -667,7 +841,7 @@ public function run(Request $request, Response $response): static
foreach (self::$options as $option) { // Global options hooks
/** @var Hook $option */
if (in_array('*', $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
+ \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
}
}
} catch (\Throwable $e) {
@@ -676,8 +850,8 @@ public function run(Request $request, Response $response): static
if (in_array('*', $error->getGroups())) {
self::setResource('error', function () use ($e) {
return $e;
- });
- \call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
+ }, [], $context);
+ \call_user_func_array($error->getAction(), $this->getArguments($error, $context, [], $request->getParams()));
}
}
}
@@ -690,23 +864,25 @@ public function run(Request $request, Response $response): static
$this->route = $route;
$path = \parse_url($request->getURI(), PHP_URL_PATH);
$route->path($path);
+
+ self::setResource('route', fn () => $route, [], $context);
}
if (null !== $route) {
- return $this->execute($route, $request, $response);
+ return $this->execute($route, $request, $context);
} elseif (self::REQUEST_METHOD_OPTIONS == $method) {
try {
foreach ($groups as $group) {
foreach (self::$options as $option) { // Group options hooks
if (in_array($group, $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
+ \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
}
}
}
foreach (self::$options as $option) { // Global options hooks
if (in_array('*', $option->getGroups())) {
- \call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
+ \call_user_func_array($option->getAction(), $this->getArguments($option, $context, [], $request->getParams()));
}
}
} catch (\Throwable $e) {
@@ -714,8 +890,8 @@ public function run(Request $request, Response $response): static
if (in_array('*', $error->getGroups())) {
self::setResource('error', function () use ($e) {
return $e;
- });
- \call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
+ }, [], $context);
+ \call_user_func_array($error->getAction(), $this->getArguments($error, $context, [], $request->getParams()));
}
}
}
@@ -724,8 +900,8 @@ public function run(Request $request, Response $response): static
if (in_array('*', $error->getGroups())) {
self::setResource('error', function () {
return new Exception('Not Found', 404);
- });
- \call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
+ }, [], $context);
+ \call_user_func_array($error->getAction(), $this->getArguments($error, $context, [], $request->getParams()));
}
}
}
@@ -745,7 +921,7 @@ public function run(Request $request, Response $response): static
*
* @throws Exception
*/
- protected function validate(string $key, array $param, mixed $value): void
+ protected function validate(string $key, array $param, mixed $value, $context): void
{
if ($param['optional'] && \is_null($value)) {
return;
@@ -754,7 +930,7 @@ protected function validate(string $key, array $param, mixed $value): void
$validator = $param['validator']; // checking whether the class exists
if (\is_callable($validator)) {
- $validator = \call_user_func_array($validator, $this->getResources($param['injections']));
+ $validator = \call_user_func_array($validator, $this->getResources($param['injections'], $context));
}
if (!$validator instanceof Validator) { // is the validator object an instance of the Validator class
@@ -780,5 +956,6 @@ public static function reset(): void
self::$init = [];
self::$shutdown = [];
self::$options = [];
+ self::$startHooks = [];
}
}
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..65f67b28 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
@@ -562,51 +480,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 +514,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 96%
rename from src/Response.php
rename to src/Http/Response.php
index b9c7ba84..e6e7f2ad 100755
--- a/src/Response.php
+++ b/src/Http/Response.php
@@ -1,8 +1,8 @@
*/
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/Http/Validator.php
similarity index 97%
rename from src/Validator.php
rename to src/Http/Validator.php
index d58dfea7..6a8304ea 100755
--- a/src/Validator.php
+++ b/src/Http/Validator.php
@@ -1,6 +1,6 @@
length = $length;
+ }
+
/**
* Get Description
*
@@ -64,7 +79,7 @@ public function isValid($value): bool
$jsonString = \json_encode($value);
$jsonStringSize = \strlen($jsonString);
- if ($jsonStringSize > 65535) {
+ if ($jsonStringSize > $this->length) {
return false;
}
diff --git a/src/Validator/Boolean.php b/src/Http/Validator/Boolean.php
similarity index 96%
rename from src/Validator/Boolean.php
rename to src/Http/Validator/Boolean.php
index 50c1c986..38b498ec 100644
--- a/src/Validator/Boolean.php
+++ b/src/Http/Validator/Boolean.php
@@ -1,8 +1,8 @@
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): static
- {
- if (\strpos($key, '.') !== false) {
- throw new Exception('$key can\'t contain a dot "." character');
- }
-
- $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..458cf9ad 100644
--- a/tests/HookTest.php
+++ b/tests/HookTest.php
@@ -1,10 +1,10 @@
app = new App('Asia/Tel_Aviv');
+ Http::reset();
+ $this->http = new Http(new Server(), 'Asia/Tel_Aviv');
$this->saveRequest();
}
public function tearDown(): void
{
- $this->app = null;
+ $this->http = null;
$this->restoreRequest();
}
@@ -42,31 +44,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,27 +76,27 @@ public function testCanGetEnvironmentVariable(): void
// Mock
$_SERVER['key'] = 'value';
- $this->assertEquals(App::getEnv('key'), 'value');
- $this->assertEquals(App::getEnv('unknown', 'test'), 'test');
+ $this->assertEquals(Http::getEnv('key'), 'value');
+ $this->assertEquals(Http::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');
+ Http::setResource('rand', fn () => rand());
+ Http::setResource('first', fn ($second) => "first-{$second}", ['second']);
+ Http::setResource('second', fn () => 'second');
- $second = $this->app->getResource('second');
- $first = $this->app->getResource('first');
+ $second = $this->http->getResource('second', '1');
+ $first = $this->http->getResource('first', '1');
$this->assertEquals('second', $second);
$this->assertEquals('first-second', $first);
- $resource = $this->app->getResource('rand');
+ $resource = $this->http->getResource('rand', '1');
$this->assertNotEmpty($resource);
- $this->assertEquals($resource, $this->app->getResource('rand'));
- $this->assertEquals($resource, $this->app->getResource('rand'));
- $this->assertEquals($resource, $this->app->getResource('rand'));
+ $this->assertEquals($resource, $this->http->getResource('rand', '1'));
+ $this->assertEquals($resource, $this->http->getResource('rand', '1'));
+ $this->assertEquals($resource, $this->http->getResource('rand', '1'));
// Default Params
$route = new Route('GET', '/path');
@@ -108,7 +110,7 @@ public function testCanGetResources(): void
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request(), '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -117,10 +119,10 @@ public function testCanGetResources(): void
public function testCanExecuteRoute(): void
{
- App::setResource('rand', fn () => rand());
- $resource = $this->app->getResource('rand');
+ Http::setResource('rand', fn () => rand());
+ $resource = $this->http->getResource('rand', '1');
- $this->app
+ $this->http
->error()
->inject('error')
->action(function ($error) {
@@ -138,12 +140,12 @@ public function testCanExecuteRoute(): void
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request(), '1');
$result = \ob_get_contents();
\ob_end_clean();
// With Params
-
+ $resource = $this->http->getResource('rand', '1');
$route = new Route('GET', '/path');
$route
@@ -160,16 +162,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, '1');
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals($resource . '-param-x-param-y', $result);
// With Error
-
+ $resource = $this->http->getResource('rand', '1');
$route = new Route('GET', '/path');
$route
@@ -180,51 +182,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, '1');
$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->http->getResource('rand', '1');
+ $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 +254,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, '1');
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals('init-' . $resource . '-(init-api)-param-x-param-y-(shutdown-api)-shutdown', $result);
+ $resource = $this->http->getResource('rand', '1');
\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, '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -272,13 +275,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 +296,7 @@ public function testCanAddAndExecuteHooks()
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request(), '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -309,7 +312,7 @@ public function testCanAddAndExecuteHooks()
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request(), '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -318,51 +321,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 +380,7 @@ public function testCanHookThrowExceptions()
});
\ob_start();
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request(), '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -385,7 +388,7 @@ public function testCanHookThrowExceptions()
\ob_start();
$_GET['y'] = 'y-def';
- $this->app->execute($route, new Request(), new Response());
+ $this->http->execute($route, new Request(), '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -396,26 +399,26 @@ 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']
];
}
@@ -428,28 +431,28 @@ public function testCanMatchRoute(string $method, string $path, string $url = nu
$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 +473,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 +525,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(), '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -547,34 +550,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();
+ Http::setResource('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 +569,7 @@ public function testWildcardRoute(): void
});
\ob_start();
- @$this->app->run(new Request(), new Response());
+ @$this->http->run(new Request(), new Response(), '1');
$result = \ob_get_contents();
\ob_end_clean();
@@ -595,22 +578,12 @@ 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(), '1');
$result = \ob_get_contents();
\ob_end_clean();
$this->assertEquals('', $result);
- $_SERVER['REQUEST_METHOD'] = 'GET';
- $_SERVER['REQUEST_URI'] = '/init_response';
-
- \ob_start();
- @$this->app->run(new Request(), new Response());
- $result = \ob_get_contents();
- \ob_end_clean();
-
- $this->assertEquals('THIS IS RESPONSE FROM INIT!', $result);
-
$_SERVER['REQUEST_METHOD'] = $method;
$_SERVER['REQUEST_URI'] = $uri;
}
diff --git a/tests/RequestTest.php b/tests/RequestTest.php
index 727d534a..55d49ee9 100755
--- a/tests/RequestTest.php
+++ b/tests/RequestTest.php
@@ -1,9 +1,9 @@
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,7 +32,7 @@ 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
@@ -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..4a966b87 100755
--- a/tests/RouteTest.php
+++ b/tests/RouteTest.php
@@ -1,9 +1,9 @@
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/UtopiaRequestTest.php
index 19ae0f6b..4c78751d 100644
--- a/tests/UtopiaRequestTest.php
+++ b/tests/UtopiaRequestTest.php
@@ -1,10 +1,10 @@
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->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $arrayList->getType());
$this->assertInstanceOf(Text::class, $arrayList->getValidator());
}
@@ -25,7 +25,7 @@ public function testCanValidateNumericValues(): void
$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->assertEquals(\Utopia\Http\Validator::TYPE_MIXED, $arrayList->getType());
$this->assertInstanceOf(Numeric::class, $arrayList->getValidator());
}
@@ -35,7 +35,7 @@ public function testCanValidateNumericValuesWithBoundaries(): void
$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->assertEquals($arrayList->getType(), \Utopia\Http\Validator::TYPE_MIXED);
$this->assertInstanceOf(Numeric::class, $arrayList->getValidator());
}
}
diff --git a/tests/Validator/AssocTest.php b/tests/Validator/AssocTest.php
index 8bf1c96e..2036998b 100755
--- a/tests/Validator/AssocTest.php
+++ b/tests/Validator/AssocTest.php
@@ -1,6 +1,6 @@
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());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_ARRAY, $this->assoc->getType());
}
public function testCantValidateSequentialArray(): void
diff --git a/tests/Validator/BooleanTest.php b/tests/Validator/BooleanTest.php
index b1742918..bda71580 100755
--- a/tests/Validator/BooleanTest.php
+++ b/tests/Validator/BooleanTest.php
@@ -1,6 +1,6 @@
assertFalse($boolean->isValid('string'));
$this->assertFalse($boolean->isValid(1.2));
$this->assertFalse($boolean->isArray());
- $this->assertEquals($boolean->getType(), \Utopia\Validator::TYPE_BOOLEAN);
+ $this->assertEquals($boolean->getType(), \Utopia\Http\Validator::TYPE_BOOLEAN);
}
public function testCanValidateLoosely()
@@ -41,6 +41,6 @@ public function testCanValidateLoosely()
$this->assertFalse($boolean->isValid('string'));
$this->assertFalse($boolean->isValid(1.2));
$this->assertFalse($boolean->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_BOOLEAN, $boolean->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_BOOLEAN, $boolean->getType());
}
}
diff --git a/tests/Validator/DomainTest.php b/tests/Validator/DomainTest.php
index ca14a20e..d8fa4de4 100644
--- a/tests/Validator/DomainTest.php
+++ b/tests/Validator/DomainTest.php
@@ -11,7 +11,7 @@
* @license The MIT License (MIT)
*/
-namespace Utopia\Validator;
+namespace Utopia\Http\Validator;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Validator/FloatValidatorTest.php b/tests/Validator/FloatValidatorTest.php
index 80d8f4dd..be905b68 100755
--- a/tests/Validator/FloatValidatorTest.php
+++ b/tests/Validator/FloatValidatorTest.php
@@ -1,6 +1,6 @@
assertFalse($validator->isValid('23.5'));
$this->assertFalse($validator->isValid('23'));
$this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_FLOAT, $validator->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $validator->getType());
}
public function testCanValidateLoosely(): void
@@ -34,6 +34,6 @@ public function testCanValidateLoosely(): void
$this->assertFalse($validator->isValid('abc'));
$this->assertFalse($validator->isValid(true));
$this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_FLOAT, $validator->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $validator->getType());
}
}
diff --git a/tests/Validator/HexColorTest.php b/tests/Validator/HexColorTest.php
index 93b7aa64..f90d92fe 100755
--- a/tests/Validator/HexColorTest.php
+++ b/tests/Validator/HexColorTest.php
@@ -1,6 +1,6 @@
assertFalse($hexColor->isValid('ffff'));
$this->assertFalse($hexColor->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_STRING, $hexColor->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $hexColor->getType());
}
}
diff --git a/tests/Validator/HostTest.php b/tests/Validator/HostTest.php
index 6ad38299..7d845f33 100644
--- a/tests/Validator/HostTest.php
+++ b/tests/Validator/HostTest.php
@@ -11,7 +11,7 @@
* @license The MIT License (MIT)
*/
-namespace Utopia\Validator;
+namespace Utopia\Http\Validator;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Validator/HostnameTest.php b/tests/Validator/HostnameTest.php
index a1e492e0..2760648d 100755
--- a/tests/Validator/HostnameTest.php
+++ b/tests/Validator/HostnameTest.php
@@ -1,6 +1,6 @@
assertEquals(\Utopia\Validator::TYPE_STRING, $validator->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $validator->getType());
$this->assertFalse($validator->isArray());
$this->assertTrue($validator->isValid('myweb.com'));
diff --git a/tests/Validator/IPTest.php b/tests/Validator/IPTest.php
index 124be488..88c71994 100644
--- a/tests/Validator/IPTest.php
+++ b/tests/Validator/IPTest.php
@@ -11,7 +11,7 @@
* @license The MIT License (MIT)
*/
-namespace Utopia\Validator;
+namespace Utopia\Http\Validator;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Validator/IntegerTest.php b/tests/Validator/IntegerTest.php
index 4161b3ad..faddca2c 100755
--- a/tests/Validator/IntegerTest.php
+++ b/tests/Validator/IntegerTest.php
@@ -1,6 +1,6 @@
assertFalse($validator->isValid(true));
$this->assertFalse($validator->isValid(false));
$this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_INTEGER, $validator->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $validator->getType());
}
public function testCanValidateLoosely()
@@ -31,6 +31,6 @@ public function testCanValidateLoosely()
$this->assertFalse($validator->isValid(true));
$this->assertFalse($validator->isValid(false));
$this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_INTEGER, $validator->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $validator->getType());
}
}
diff --git a/tests/Validator/JSONTest.php b/tests/Validator/JSONTest.php
index bafce37c..d4a4e358 100755
--- a/tests/Validator/JSONTest.php
+++ b/tests/Validator/JSONTest.php
@@ -1,6 +1,6 @@
assertFalse($json->isValid(1.2));
$this->assertFalse($json->isValid("{'test': 'demo'}"));
$this->assertFalse($json->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_OBJECT, $json->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_OBJECT, $json->getType());
}
}
diff --git a/tests/Validator/MultipleTest.php b/tests/Validator/MultipleTest.php
index 8ef1ee59..348c3244 100644
--- a/tests/Validator/MultipleTest.php
+++ b/tests/Validator/MultipleTest.php
@@ -1,6 +1,6 @@
assertFalse($numeric->isValid('not numeric'));
$this->assertFalse($numeric->isValid([]));
$this->assertFalse($numeric->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_MIXED, $numeric->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_MIXED, $numeric->getType());
}
}
diff --git a/tests/Validator/RangeTest.php b/tests/Validator/RangeTest.php
index 2bd1ee68..aafbab4a 100755
--- a/tests/Validator/RangeTest.php
+++ b/tests/Validator/RangeTest.php
@@ -1,6 +1,6 @@
assertTrue($range->isValid(0));
@@ -22,13 +22,13 @@ public function testCanValidateIntegerRange()
$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());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $range->getFormat());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_INTEGER, $range->getType());
}
public function testCanValidateFloatRange()
{
- $range = new Range(0, 1, \Utopia\Validator::TYPE_FLOAT);
+ $range = new Range(0, 1, \Utopia\Http\Validator::TYPE_FLOAT);
$this->assertTrue($range->isValid(0.0));
$this->assertTrue($range->isValid(1.0));
@@ -41,14 +41,14 @@ public function testCanValidateFloatRange()
$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);
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $range->getFormat());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_FLOAT, $range->getType(), \Utopia\Http\Validator::TYPE_FLOAT);
}
public function canValidateInfinityRange()
{
- $integer = new Range(5, INF, \Utopia\Validator::TYPE_INTEGER);
- $float = new Range(-INF, 45.6, \Utopia\Validator::TYPE_FLOAT);
+ $integer = new Range(5, INF, \Utopia\Http\Validator::TYPE_INTEGER);
+ $float = new Range(-INF, 45.6, \Utopia\Http\Validator::TYPE_FLOAT);
$this->assertTrue($integer->isValid(25));
$this->assertFalse($integer->isValid(3));
diff --git a/tests/Validator/TextTest.php b/tests/Validator/TextTest.php
index cb987043..476cfd8a 100755
--- a/tests/Validator/TextTest.php
+++ b/tests/Validator/TextTest.php
@@ -1,6 +1,6 @@
assertFalse($validator->isValid(['seven', 8, 9.0]));
$this->assertFalse($validator->isValid(false));
$this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_STRING, $validator->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $validator->getType());
}
public function testCanValidateBoundaries(): void
diff --git a/tests/Validator/URLTest.php b/tests/Validator/URLTest.php
index e7bad1b5..f092890f 100644
--- a/tests/Validator/URLTest.php
+++ b/tests/Validator/URLTest.php
@@ -11,7 +11,7 @@
* @license The MIT License (MIT)
*/
-namespace Utopia\Validator;
+namespace Utopia\Http\Validator;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Validator/WhiteListTest.php b/tests/Validator/WhiteListTest.php
index 9d88f75e..f024908c 100755
--- a/tests/Validator/WhiteListTest.php
+++ b/tests/Validator/WhiteListTest.php
@@ -1,6 +1,6 @@
assertFalse($whiteList->isValid(5));
$this->assertFalse($whiteList->isArray());
$this->assertEquals($whiteList->getList(), ['string1', 'string2', 3, 4]);
- $this->assertEquals(\Utopia\Validator::TYPE_STRING, $whiteList->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $whiteList->getType());
}
public function testCanValidateLoosely(): void
diff --git a/tests/Validator/WildcardTest.php b/tests/Validator/WildcardTest.php
index b79c3580..4de71475 100644
--- a/tests/Validator/WildcardTest.php
+++ b/tests/Validator/WildcardTest.php
@@ -1,6 +1,6 @@
assertTrue($validator->isValid(true));
$this->assertTrue($validator->isValid(false));
$this->assertFalse($validator->isArray());
- $this->assertEquals(\Utopia\Validator::TYPE_STRING, $validator->getType());
+ $this->assertEquals(\Utopia\Http\Validator::TYPE_STRING, $validator->getType());
}
}
diff --git a/tests/ViewTest.php b/tests/ViewTest.php
deleted file mode 100755
index 9b18fb1e..00000000
--- a/tests/ViewTest.php
+++ /dev/null
@@ -1,86 +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));
- }
-}
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/e2e/ResponseTest.php b/tests/e2e/BaseTest.php
similarity index 82%
rename from tests/e2e/ResponseTest.php
rename to tests/e2e/BaseTest.php
index 878cc23f..3fbdef51 100644
--- a/tests/e2e/ResponseTest.php
+++ b/tests/e2e/BaseTest.php
@@ -1,19 +1,11 @@
client = new Client();
- }
-
public function testResponse()
{
$response = $this->client->call(Client::METHOD_GET, '/');
diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php
index 4052689b..5bf15b2e 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;
}
/**
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/server.php b/tests/e2e/init.php
similarity index 75%
rename from tests/e2e/server.php
rename to tests/e2e/init.php
index cef864ee..64bb3cfa 100644
--- a/tests/e2e/server.php
+++ b/tests/e2e/init.php
@@ -2,10 +2,9 @@
require_once __DIR__.'/../../vendor/autoload.php';
-use Utopia\App;
-use Utopia\Request;
-use Utopia\Response;
-use Utopia\Validator\Text;
+use Utopia\Http\Http;
+use Utopia\Http\Response;
+use Utopia\Http\Validator\Text;
ini_set('memory_limit', '1024M');
ini_set('display_errors', '1');
@@ -13,20 +12,20 @@
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('/chunked')
->inject('response')
->action(function (Response $response) {
foreach (['Hello ', 'World!'] as $key => $word) {
@@ -34,20 +33,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..b886c118
--- /dev/null
+++ b/tests/e2e/server-fpm.php
@@ -0,0 +1,10 @@
+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