Compare commits
2 Commits
b6d1215999
...
bcbf807aac
| Author | SHA1 | Date | |
|---|---|---|---|
| bcbf807aac | |||
| d405d58f8d |
91
.gitignore
vendored
91
.gitignore
vendored
@@ -1,89 +1,2 @@
|
|||||||
vendor/autoload.php
|
/vendor
|
||||||
vendor/clue/ndjson-react/CHANGELOG.md
|
.env
|
||||||
vendor/clue/ndjson-react/composer.json
|
|
||||||
vendor/clue/ndjson-react/LICENSE
|
|
||||||
vendor/clue/ndjson-react/README.md
|
|
||||||
vendor/clue/ndjson-react/.github/FUNDING.yml
|
|
||||||
vendor/clue/ndjson-react/src/Decoder.php
|
|
||||||
vendor/clue/ndjson-react/src/Encoder.php
|
|
||||||
vendor/clue/reactphp-sqlite/CHANGELOG.md
|
|
||||||
vendor/clue/reactphp-sqlite/composer.json
|
|
||||||
vendor/clue/reactphp-sqlite/LICENSE
|
|
||||||
vendor/clue/reactphp-sqlite/README.md
|
|
||||||
vendor/clue/reactphp-sqlite/.github/FUNDING.yml
|
|
||||||
vendor/clue/reactphp-sqlite/res/sqlite-worker.php
|
|
||||||
vendor/clue/reactphp-sqlite/src/DatabaseInterface.php
|
|
||||||
vendor/clue/reactphp-sqlite/src/Factory.php
|
|
||||||
vendor/clue/reactphp-sqlite/src/Result.php
|
|
||||||
vendor/clue/reactphp-sqlite/src/Io/BlockingDatabase.php
|
|
||||||
vendor/clue/reactphp-sqlite/src/Io/LazyDatabase.php
|
|
||||||
vendor/clue/reactphp-sqlite/src/Io/ProcessIoDatabase.php
|
|
||||||
vendor/composer/autoload_classmap.php
|
|
||||||
vendor/composer/autoload_files.php
|
|
||||||
vendor/composer/autoload_namespaces.php
|
|
||||||
vendor/composer/autoload_psr4.php
|
|
||||||
vendor/composer/autoload_real.php
|
|
||||||
vendor/composer/autoload_static.php
|
|
||||||
vendor/composer/ClassLoader.php
|
|
||||||
vendor/composer/installed.json
|
|
||||||
vendor/composer/installed.php
|
|
||||||
vendor/composer/InstalledVersions.php
|
|
||||||
vendor/composer/LICENSE
|
|
||||||
vendor/composer/platform_check.php
|
|
||||||
vendor/evenement/evenement/.gitattributes
|
|
||||||
vendor/evenement/evenement/composer.json
|
|
||||||
vendor/evenement/evenement/LICENSE
|
|
||||||
vendor/evenement/evenement/README.md
|
|
||||||
vendor/evenement/evenement/src/EventEmitter.php
|
|
||||||
vendor/evenement/evenement/src/EventEmitterInterface.php
|
|
||||||
vendor/evenement/evenement/src/EventEmitterTrait.php
|
|
||||||
vendor/react/child-process/CHANGELOG.md
|
|
||||||
vendor/react/child-process/composer.json
|
|
||||||
vendor/react/child-process/LICENSE
|
|
||||||
vendor/react/child-process/README.md
|
|
||||||
vendor/react/child-process/src/Process.php
|
|
||||||
vendor/react/event-loop/CHANGELOG.md
|
|
||||||
vendor/react/event-loop/composer.json
|
|
||||||
vendor/react/event-loop/LICENSE
|
|
||||||
vendor/react/event-loop/README.md
|
|
||||||
vendor/react/event-loop/src/ExtEventLoop.php
|
|
||||||
vendor/react/event-loop/src/ExtEvLoop.php
|
|
||||||
vendor/react/event-loop/src/ExtLibeventLoop.php
|
|
||||||
vendor/react/event-loop/src/ExtLibevLoop.php
|
|
||||||
vendor/react/event-loop/src/ExtUvLoop.php
|
|
||||||
vendor/react/event-loop/src/Factory.php
|
|
||||||
vendor/react/event-loop/src/Loop.php
|
|
||||||
vendor/react/event-loop/src/LoopInterface.php
|
|
||||||
vendor/react/event-loop/src/SignalsHandler.php
|
|
||||||
vendor/react/event-loop/src/StreamSelectLoop.php
|
|
||||||
vendor/react/event-loop/src/TimerInterface.php
|
|
||||||
vendor/react/event-loop/src/Tick/FutureTickQueue.php
|
|
||||||
vendor/react/event-loop/src/Timer/Timer.php
|
|
||||||
vendor/react/event-loop/src/Timer/Timers.php
|
|
||||||
vendor/react/promise/CHANGELOG.md
|
|
||||||
vendor/react/promise/composer.json
|
|
||||||
vendor/react/promise/LICENSE
|
|
||||||
vendor/react/promise/README.md
|
|
||||||
vendor/react/promise/src/Deferred.php
|
|
||||||
vendor/react/promise/src/functions_include.php
|
|
||||||
vendor/react/promise/src/functions.php
|
|
||||||
vendor/react/promise/src/Promise.php
|
|
||||||
vendor/react/promise/src/PromiseInterface.php
|
|
||||||
vendor/react/promise/src/Exception/CompositeException.php
|
|
||||||
vendor/react/promise/src/Exception/LengthException.php
|
|
||||||
vendor/react/promise/src/Internal/CancellationQueue.php
|
|
||||||
vendor/react/promise/src/Internal/FulfilledPromise.php
|
|
||||||
vendor/react/promise/src/Internal/RejectedPromise.php
|
|
||||||
vendor/react/stream/CHANGELOG.md
|
|
||||||
vendor/react/stream/composer.json
|
|
||||||
vendor/react/stream/LICENSE
|
|
||||||
vendor/react/stream/README.md
|
|
||||||
vendor/react/stream/src/CompositeStream.php
|
|
||||||
vendor/react/stream/src/DuplexResourceStream.php
|
|
||||||
vendor/react/stream/src/DuplexStreamInterface.php
|
|
||||||
vendor/react/stream/src/ReadableResourceStream.php
|
|
||||||
vendor/react/stream/src/ReadableStreamInterface.php
|
|
||||||
vendor/react/stream/src/ThroughStream.php
|
|
||||||
vendor/react/stream/src/Util.php
|
|
||||||
vendor/react/stream/src/WritableResourceStream.php
|
|
||||||
vendor/react/stream/src/WritableStreamInterface.php
|
|
||||||
2
vendor/clue/framework-x/.github/FUNDING.yml
vendored
2
vendor/clue/framework-x/.github/FUNDING.yml
vendored
@@ -1,2 +0,0 @@
|
|||||||
github: clue
|
|
||||||
custom: https://clue.engineering/support
|
|
||||||
373
vendor/clue/framework-x/CHANGELOG.md
vendored
373
vendor/clue/framework-x/CHANGELOG.md
vendored
@@ -1,373 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## 0.16.0 (2024-03-05)
|
|
||||||
|
|
||||||
We are thrilled to announce the official release of `v0.16.0` to the public! 🎉🚀
|
|
||||||
Additionally, we are making all previous tagged versions available to simplify the upgrade process.
|
|
||||||
In addition to the release of `v0.16.0`, this update includes all prior tagged releases.
|
|
||||||
|
|
||||||
This release includes exciting new features such as improved performance, additional options
|
|
||||||
for access logging, updates to our documentation and nginx + Apache configurations,
|
|
||||||
as well as many more internal improvements to our test suite and integration tests.
|
|
||||||
|
|
||||||
* Feature: Improve performance by skipping `AccessLogHandler` if it writes to `/dev/null`.
|
|
||||||
(#248 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add optional `$path` argument for `AccessLogHandler`.
|
|
||||||
(#247 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements and update nginx + Apache configuration.
|
|
||||||
(#245 and #251 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite with improved directory structure for integration tests.
|
|
||||||
(#250 by @clue)
|
|
||||||
|
|
||||||
## 0.15.0 (2023-12-07)
|
|
||||||
|
|
||||||
* Feature: Full PHP 8.3 compatibility.
|
|
||||||
(#244 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add `App::__invoke()` method to enable custom integrations.
|
|
||||||
(#236 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve performance by only using `FiberHandler` for `ReactiveHandler`.
|
|
||||||
(#237 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#242 by @yadaiio)
|
|
||||||
|
|
||||||
## 0.14.0 (2023-07-31)
|
|
||||||
|
|
||||||
* Feature: Improve Promise v3 support and use Promise v3 template types.
|
|
||||||
(#233 and #235 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve handling `OPTIONS *` requests.
|
|
||||||
(#226 by @clue)
|
|
||||||
|
|
||||||
* Refactor logging into new `LogStreamHandler` and reactive server logic into new `ReactiveHandler`.
|
|
||||||
(#222 and #224 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite and ensure 100% code coverage.
|
|
||||||
(#217, #221, #225 and #228 by @clue)
|
|
||||||
|
|
||||||
## 0.13.0 (2023-02-22)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with upcoming Promise v3.
|
|
||||||
(#188 by @clue)
|
|
||||||
|
|
||||||
* Feature: Full PHP 8.2 compatibility.
|
|
||||||
(#194 and #207 by @clue)
|
|
||||||
|
|
||||||
* Feature: Load environment variables from `$_ENV`, `$_SERVER` and `getenv()`.
|
|
||||||
(#205 by @clue)
|
|
||||||
|
|
||||||
* Feature: Update to support `Content-Length` response header on `HEAD` requests.
|
|
||||||
(#186 by @clue)
|
|
||||||
|
|
||||||
* Feature / Fix: Consistent handling for HTTP responses with multiple header values (PHP SAPI).
|
|
||||||
(#214 by @pfk84)
|
|
||||||
|
|
||||||
* Fix: Respect explicit response status code when Location response header is given (PHP SAPI).
|
|
||||||
(#191 by @jkrzefski)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#189 by @clue)
|
|
||||||
|
|
||||||
* Add PHPStan to test environment on level `max` and improve type definitions.
|
|
||||||
(#200, #201 and #204 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite and report failed assertions.
|
|
||||||
(#199 by @clue and #208 by @SimonFrings)
|
|
||||||
|
|
||||||
## 0.12.0 (2022-08-03)
|
|
||||||
|
|
||||||
* Feature: Support loading environment variables from DI container configuration.
|
|
||||||
(#184 by @clue)
|
|
||||||
|
|
||||||
* Feature: Support typed container variables for container factory functions.
|
|
||||||
(#178, #179 and #180 by @clue)
|
|
||||||
|
|
||||||
* Feature: Support nullable and `null` arguments and default values for DI container configuration.
|
|
||||||
(#181 and #183 by @clue)
|
|
||||||
|
|
||||||
* Feature: Support untyped and `mixed` arguments for container factory.
|
|
||||||
(#182 by @clue)
|
|
||||||
|
|
||||||
## 0.11.0 (2022-07-26)
|
|
||||||
|
|
||||||
* Feature: Make `AccessLogHandler` and `ErrorHandler` part of public API.
|
|
||||||
(#173 and #174 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
$app = new FrameworkX\App(
|
|
||||||
new FrameworkX\AccessLogHandler(),
|
|
||||||
new FrameworkX\ErrorHandler()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register routes here, see routing…
|
|
||||||
|
|
||||||
$app->run();
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Support loading `AccessLogHandler` and `ErrorHandler` from `Container`.
|
|
||||||
(#175 by @clue)
|
|
||||||
|
|
||||||
* Feature: Read `$remote_addr` attribute for `AccessLogHandler` (trusted proxies).
|
|
||||||
(#177 by @clue)
|
|
||||||
|
|
||||||
* Internal refactoring to move all handlers to `Io` namespace.
|
|
||||||
(#176 by @clue)
|
|
||||||
|
|
||||||
* Update test suite to remove deprecated `utf8_decode()` (PHP 8.2 preparation).
|
|
||||||
(#171 by SimonFrings)
|
|
||||||
|
|
||||||
## 0.10.0 (2022-07-14)
|
|
||||||
|
|
||||||
* Feature: Built-in support for fibers on PHP 8.1+ with stable reactphp/async.
|
|
||||||
(#168 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
$app->get('/book/{isbn}', function (Psr\Http\Message\ServerRequestInterface $request) use ($db) {
|
|
||||||
$isbn = $request->getAttribute('isbn');
|
|
||||||
$result = await($db->query(
|
|
||||||
'SELECT title FROM book WHERE isbn = ?',
|
|
||||||
[$isbn]
|
|
||||||
));
|
|
||||||
|
|
||||||
assert($result instanceof React\MySQL\QueryResult);
|
|
||||||
$data = $result->resultRows[0]['title'];
|
|
||||||
|
|
||||||
return React\Http\Message\Response::plaintext(
|
|
||||||
$data
|
|
||||||
);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Support PSR-11 container interface by using DI container as adapter.
|
|
||||||
(#163 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#158 by @clue and #160 by @SimonFrings)
|
|
||||||
|
|
||||||
## 0.9.0 (2022-05-13)
|
|
||||||
|
|
||||||
* Feature: Add signal handling support for `SIGINT` and `SIGTERM`.
|
|
||||||
(#150 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve error output for exception messages with special characters.
|
|
||||||
(#131 by @clue)
|
|
||||||
|
|
||||||
* Add new documentation chapters for Docker containers and HTTP redirecting.
|
|
||||||
(#138 by SimonFrings and #136, #151 and #156 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#143 by @zf2timo, #153 by @mattschlosser and #129 and #154 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite and add tests for `Dockerfile` instructions.
|
|
||||||
(#148 and #149 by @clue)
|
|
||||||
|
|
||||||
## 0.8.0 (2022-03-07)
|
|
||||||
|
|
||||||
* Feature: Automatically start new fiber for each request on PHP 8.1+.
|
|
||||||
(#117 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add fiber compatibility mode for PHP < 8.1.
|
|
||||||
(#128 by @clue)
|
|
||||||
|
|
||||||
* Improve documentation and update installation instructions for react/async.
|
|
||||||
(#116 and #126 by @clue and #124, #125 and #127 by @SimonFrings)
|
|
||||||
|
|
||||||
* Improve fiber tests to avoid now unneeded `await()` calls.
|
|
||||||
(#118 by @clue)
|
|
||||||
|
|
||||||
## 0.7.0 (2022-02-05)
|
|
||||||
|
|
||||||
* Feature: Update to use HTTP status code constants and JSON/HTML response helpers.
|
|
||||||
(#114 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
$app->get('/users/{name}', function (Psr\Http\Message\ServerRequestInterface $request) {
|
|
||||||
return React\Http\Message\Response::plaintext(
|
|
||||||
"Hello " . $request->getAttribute('name') . "!\n"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature / Fix: Update to improve protocol handling for HTTP responses with no body.
|
|
||||||
(#113 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#112 by @SimonFrings and #115 by @netcarver)
|
|
||||||
|
|
||||||
## 0.6.0 (2021-12-20)
|
|
||||||
|
|
||||||
* Feature: Support automatic dependency injection by using class names (DI container).
|
|
||||||
(#89, #92 and #94 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
$app = new FrameworkX\App(Acme\Todo\JsonMiddleware::class);
|
|
||||||
|
|
||||||
$app->get('/', Acme\Todo\HelloController::class);
|
|
||||||
$app->get('/users/{name}', Acme\Todo\UserController::class);
|
|
||||||
|
|
||||||
$app->run();
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Add support for explicit DI container configuration.
|
|
||||||
(#95, #96 and #97 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
$container = new FrameworkX\Container([
|
|
||||||
Acme\Todo\HelloController::class => fn() => new Acme\Todo\HelloController();
|
|
||||||
Acme\Todo\UserController::class => function (React\Http\Browser $browser) {
|
|
||||||
// example UserController class requires two arguments:
|
|
||||||
// - first argument will be autowired based on class reference
|
|
||||||
// - second argument expects some manual value
|
|
||||||
return new Acme\Todo\UserController($browser, 42);
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
// …
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Refactor to use `$_SERVER` instead of `getenv()`.
|
|
||||||
(#91 by @bpolaszek)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#100 by @clue)
|
|
||||||
|
|
||||||
* Update test suite to use stable PHP 8.1 Docker image.
|
|
||||||
(#90 by @clue)
|
|
||||||
|
|
||||||
## 0.5.0 (2021-11-30)
|
|
||||||
|
|
||||||
* Feature / BC break: Simplify `App` by always using default loop, drop optional loop instance.
|
|
||||||
(#88 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// old
|
|
||||||
$loop = React\EventLoop\Loop::get();
|
|
||||||
$app = new FrameworkX\App($loop);
|
|
||||||
|
|
||||||
// new (already supported before)
|
|
||||||
$app = new FrameworkX\App();
|
|
||||||
```
|
|
||||||
|
|
||||||
* Add documentation for manual restart of systemd service and chapter for Caddy deployment.
|
|
||||||
(#87 by @SimonFrings and #82 by @francislavoie)
|
|
||||||
|
|
||||||
* Improve documentation, remove leftover `$loop` references and fix typos.
|
|
||||||
(#72 by @shuvroroy, #80 by @Ivanshamir, #81 by @clue and #83 by @rattuscz)
|
|
||||||
|
|
||||||
## 0.4.0 (2021-11-23)
|
|
||||||
|
|
||||||
We are excited to announce the official release of Framework X to the public! 🎉🚀
|
|
||||||
This release includes exciting new features such as full compatibility with PHP 8.1,
|
|
||||||
improvements to response handling, and enhanced documentation covering nginx,
|
|
||||||
Apache, and async database usage.
|
|
||||||
|
|
||||||
* Feature: Announce Framework X public beta.
|
|
||||||
(#64 by @clue)
|
|
||||||
|
|
||||||
* Feature: Full PHP 8.1 compatibility.
|
|
||||||
(#58 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve `AccessLogHandler` and fix response size for streaming response body.
|
|
||||||
(#47, #48, #49 and #50 by @clue)
|
|
||||||
|
|
||||||
* Feature / Fix: Skip sending body and `Content-Length` for responses with no body.
|
|
||||||
(#51 by @clue)
|
|
||||||
|
|
||||||
* Feature / Fix: Consistently reject proxy requests and handle `OPTIONS *` requests.
|
|
||||||
(#46 by @clue)
|
|
||||||
|
|
||||||
* Add new documentation chapters for nginx, Apache and async database.
|
|
||||||
(#57, #59 and #60 by @clue)
|
|
||||||
|
|
||||||
* Improve documentation, examples and describe HTTP caching and output buffering.
|
|
||||||
(#52, #53, #55, #56, #61, #62 and #63 by @clue)
|
|
||||||
|
|
||||||
## 0.3.0 (2021-09-23)
|
|
||||||
|
|
||||||
* Feature: Add support for global middleware.
|
|
||||||
(#23 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve error output and refactor internal error handler.
|
|
||||||
(#37, #39 and #41 by @clue)
|
|
||||||
|
|
||||||
* Feature: Support changing listening address via new `X_LISTEN` environment variable.
|
|
||||||
(#38 by @clue)
|
|
||||||
|
|
||||||
* Feature: Update to new ReactPHP HTTP and Socket API.
|
|
||||||
(#26 and #29 by @HLeithner and #34 by @clue)
|
|
||||||
|
|
||||||
* Feature: Refactor to use new `AccessLogHandler`, `RouteHandler`, `RedirectHandler` and `SapiHandler`.
|
|
||||||
(#42, #43, #44 and #45 by @clue)
|
|
||||||
|
|
||||||
* Fix: Fix path filter regex.
|
|
||||||
(#27 by @HLeithner)
|
|
||||||
|
|
||||||
* Add documentation for async middleware and systemd service unit configuration.
|
|
||||||
(#24 by @Degra1991 and #32, #35, #36 and #40 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite and run tests on Windows with PHPUnit.
|
|
||||||
(#31 by @SimonFrings and #28 and #33 by @clue)
|
|
||||||
|
|
||||||
## 0.2.0 (2021-06-18)
|
|
||||||
|
|
||||||
* Feature: Simplify `App` usage by making `LoopInterface` argument optional.
|
|
||||||
(#22 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// old (still supported)
|
|
||||||
$loop = React\EventLoop\Factory::create();
|
|
||||||
$app = new FrameworkX\App($loop);
|
|
||||||
|
|
||||||
// new (using default loop)
|
|
||||||
$app = new FrameworkX\App();
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Add middleware support.
|
|
||||||
(#18 by @clue)
|
|
||||||
|
|
||||||
* Feature: Refactor and simplify route dispatcher.
|
|
||||||
(#21 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add Generator-based coroutine implementation.
|
|
||||||
(#17 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#15, #16 and #19 by @clue)
|
|
||||||
|
|
||||||
## 0.1.0 (2021-04-30)
|
|
||||||
|
|
||||||
We're excited to announce the release of the first version of Framework X in
|
|
||||||
private beta! This version marks the starting point of our project and is the
|
|
||||||
first of many milestones for making async PHP easier than ever before.
|
|
||||||
|
|
||||||
* Release Framework X, major documentation overhaul and improve examples.
|
|
||||||
(#14, #13 and #2 by @clue)
|
|
||||||
|
|
||||||
* Feature: Support running behind nginx and Apache (PHP-FPM and mod_php).
|
|
||||||
(#3, #11 and #12 by @clue)
|
|
||||||
|
|
||||||
* Feature / Fix: Consistently parse request URI and improve URL handling.
|
|
||||||
(#4, #5, #6 and #7 by @clue)
|
|
||||||
|
|
||||||
* Feature: Rewrite `FilesystemHandler`, improve file access and directory listing.
|
|
||||||
(#8 and #9 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add `any()` router method to match any request method.
|
|
||||||
(#10 by @clue)
|
|
||||||
21
vendor/clue/framework-x/LICENSE
vendored
21
vendor/clue/framework-x/LICENSE
vendored
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2019 Christian Lück
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
|
||||||
to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
147
vendor/clue/framework-x/README.md
vendored
147
vendor/clue/framework-x/README.md
vendored
@@ -1,147 +0,0 @@
|
|||||||
# Framework X
|
|
||||||
|
|
||||||
[](https://github.com/clue-access/framework-x/actions)
|
|
||||||
[](#tests)
|
|
||||||
|
|
||||||
Framework X – the simple and fast micro framework for building reactive web applications that run anywhere.
|
|
||||||
|
|
||||||
* [Support us](#support-us)
|
|
||||||
* [Quickstart](#quickstart)
|
|
||||||
* [Documentation](#documentation)
|
|
||||||
* [Contribute](#contribute)
|
|
||||||
* [Tests](#tests)
|
|
||||||
* [License](#license)
|
|
||||||
|
|
||||||
## Support us
|
|
||||||
|
|
||||||
We invest a lot of time developing, maintaining and updating our awesome
|
|
||||||
open-source projects. You can help us sustain this high-quality of our work by
|
|
||||||
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
|
|
||||||
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
|
|
||||||
for details.
|
|
||||||
|
|
||||||
Let's take these projects to the next level together! 🚀
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
|
|
||||||
Start by creating an empty project directory.
|
|
||||||
Next, we can start by taking a look at a simple example application.
|
|
||||||
You can use this example to get started by creating a new `public/` directory with
|
|
||||||
an `index.php` file inside:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
$app = new FrameworkX\App();
|
|
||||||
|
|
||||||
$app->get('/', function () {
|
|
||||||
return React\Http\Message\Response::plaintext(
|
|
||||||
"Hello wörld!\n"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/users/{name}', function (Psr\Http\Message\ServerRequestInterface $request) {
|
|
||||||
return React\Http\Message\Response::plaintext(
|
|
||||||
"Hello " . $request->getAttribute('name') . "!\n"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->run();
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, we need to install X and its dependencies to actually run this project.
|
|
||||||
In your project directory, simply run the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ composer require clue/framework-x:^0.16
|
|
||||||
```
|
|
||||||
|
|
||||||
> See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
|
||||||
|
|
||||||
That's it already! The next step is now to serve this web application.
|
|
||||||
One of the nice properties of this project is that is works both behind
|
|
||||||
traditional web server setups as well as in a stand-alone environment.
|
|
||||||
|
|
||||||
For example, you can run the above example using the built-in web server like
|
|
||||||
this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ php public/index.php
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now use your favorite web browser or command line tool to check your web
|
|
||||||
application responds as expected:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ curl http://localhost:8080/
|
|
||||||
Hello wörld!
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Hooked?
|
|
||||||
See [website](https://framework-x.org/) for full documentation.
|
|
||||||
|
|
||||||
Found a typo or want to contribute?
|
|
||||||
The website documentation is built from the source documentation files in
|
|
||||||
the [docs/](docs/) folder.
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
You want to contribute to the Framework X source code or documentation? You've
|
|
||||||
come to the right place!
|
|
||||||
|
|
||||||
To contribute to the source code just locate the [src/](src/) folder and you'll find all
|
|
||||||
content in there. Additionally, our [tests/](tests/) folder contains all our unit
|
|
||||||
tests and acceptance tests to assure our code works as expected. For more
|
|
||||||
information on how to run the test suite check out our [testing chapter](#tests).
|
|
||||||
|
|
||||||
If you want to contribute to the [documentation](#documentation) of Framework X
|
|
||||||
found on the website, take a look inside the [docs/](docs/) folder. You'll find further
|
|
||||||
instructions inside the `README.md` in there.
|
|
||||||
|
|
||||||
Found a typo on our [website](https://framework-x.org/)? Simply go to our
|
|
||||||
[website repository](https://github.com/clue/framework-x-website)
|
|
||||||
and follow the instructions found in the `README`.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
To run the test suite, you first need to clone this repo and then install all
|
|
||||||
dependencies [through Composer](https://getcomposer.org/):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ composer install
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the test suite, go to the project root and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ vendor/bin/phpunit
|
|
||||||
```
|
|
||||||
|
|
||||||
The test suite is set up to always ensure 100% code coverage across all
|
|
||||||
supported environments. If you have the Xdebug extension installed, you can also
|
|
||||||
generate a code coverage report locally like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, you can run our sophisticated integration tests to verify the
|
|
||||||
framework examples work as expected behind your web server. Use your web server
|
|
||||||
of choice (see deployment documentation) and execute the tests with the URL to
|
|
||||||
your installation like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ php tests/integration/public/index.php
|
|
||||||
$ tests/integration.bash http://localhost:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is released under the permissive [MIT license](LICENSE).
|
|
||||||
|
|
||||||
> Did you know that I offer custom development services and issuing invoices for
|
|
||||||
sponsorships of releases and for contributions? Contact me (@clue) for details.
|
|
||||||
40
vendor/clue/framework-x/composer.json
vendored
40
vendor/clue/framework-x/composer.json
vendored
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "clue/framework-x",
|
|
||||||
"description": "Framework X – the simple and fast micro framework for building reactive web applications that run anywhere.",
|
|
||||||
"keywords": ["microframework", "micro", "framework", "web", "http", "event-driven", "async", "ReactPHP"],
|
|
||||||
"homepage": "https://framework-x.org/",
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Christian Lück",
|
|
||||||
"email": "christian@clue.engineering"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1",
|
|
||||||
"nikic/fast-route": "^1.3",
|
|
||||||
"react/async": "^4 || ^3",
|
|
||||||
"react/http": "^1.9",
|
|
||||||
"react/promise": "^3 || ^2.10",
|
|
||||||
"react/socket": "^1.13"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpstan/phpstan": "1.10.47 || 1.4.10",
|
|
||||||
"phpunit/phpunit": "^9.6 || ^7.5",
|
|
||||||
"psr/container": "^2 || ^1",
|
|
||||||
"react/promise-timer": "^1.10"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"FrameworkX\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload-dev": {
|
|
||||||
"psr-4": {
|
|
||||||
"FrameworkX\\Tests\\": "tests/"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"tests/FiberStub.php"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
140
vendor/clue/framework-x/src/AccessLogHandler.php
vendored
140
vendor/clue/framework-x/src/AccessLogHandler.php
vendored
@@ -1,140 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX;
|
|
||||||
|
|
||||||
use FrameworkX\Io\LogStreamHandler;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
use React\Stream\ReadableStreamInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final
|
|
||||||
*/
|
|
||||||
class AccessLogHandler
|
|
||||||
{
|
|
||||||
/** @var ?LogStreamHandler */
|
|
||||||
private $logger;
|
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $hasHighResolution;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ?string $path (optional) absolute log file path or will log to console output by default
|
|
||||||
* @throws \InvalidArgumentException if given `$path` is not an absolute file path
|
|
||||||
* @throws \RuntimeException if given `$path` can not be opened in append mode
|
|
||||||
*/
|
|
||||||
public function __construct(?string $path = null)
|
|
||||||
{
|
|
||||||
if ($path === null) {
|
|
||||||
$path = \PHP_SAPI === 'cli' ? 'php://output' : 'php://stderr';
|
|
||||||
}
|
|
||||||
|
|
||||||
$logger = new LogStreamHandler($path);
|
|
||||||
if (!$logger->isDevNull()) {
|
|
||||||
// only assign logger if we're not logging to /dev/null (which would discard any logs)
|
|
||||||
$this->logger = $logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->hasHighResolution = \function_exists('hrtime'); // PHP 7.3+
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Internal] Returns whether we're writing to /dev/null (which will discard any logs)
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isDevNull(): bool
|
|
||||||
{
|
|
||||||
return $this->logger === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
|
||||||
*/
|
|
||||||
public function __invoke(ServerRequestInterface $request, callable $next)
|
|
||||||
{
|
|
||||||
if ($this->logger === null) {
|
|
||||||
// Skip if we're logging to /dev/null (which will discard any logs).
|
|
||||||
// As an additional optimization, the `App` will automatically
|
|
||||||
// detect we no longer need to invoke this instance at all.
|
|
||||||
return $next($request); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
|
|
||||||
$now = $this->now();
|
|
||||||
$response = $next($request);
|
|
||||||
|
|
||||||
if ($response instanceof PromiseInterface) {
|
|
||||||
/** @var PromiseInterface<ResponseInterface> $response */
|
|
||||||
return $response->then(function (ResponseInterface $response) use ($request, $now) {
|
|
||||||
$this->logWhenClosed($request, $response, $now);
|
|
||||||
return $response;
|
|
||||||
});
|
|
||||||
} elseif ($response instanceof \Generator) {
|
|
||||||
return (function (\Generator $generator) use ($request, $now) {
|
|
||||||
$response = yield from $generator;
|
|
||||||
$this->logWhenClosed($request, $response, $now);
|
|
||||||
return $response;
|
|
||||||
})($response);
|
|
||||||
} else {
|
|
||||||
$this->logWhenClosed($request, $response, $now);
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checks if response body is closed (not streaming) before writing log message for response
|
|
||||||
*/
|
|
||||||
private function logWhenClosed(ServerRequestInterface $request, ResponseInterface $response, float $start): void
|
|
||||||
{
|
|
||||||
$body = $response->getBody();
|
|
||||||
|
|
||||||
if ($body instanceof ReadableStreamInterface && $body->isReadable()) {
|
|
||||||
$size = 0;
|
|
||||||
$body->on('data', function (string $chunk) use (&$size) {
|
|
||||||
$size += strlen($chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
$body->on('close', function () use (&$size, $request, $response, $start) {
|
|
||||||
$this->log($request, $response, $size, $this->now() - $start);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$this->log($request, $response, $body->getSize() ?? strlen((string) $body), $this->now() - $start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* writes log message for response after response body is closed (not streaming anymore)
|
|
||||||
*/
|
|
||||||
private function log(ServerRequestInterface $request, ResponseInterface $response, int $responseSize, float $time): void
|
|
||||||
{
|
|
||||||
$method = $request->getMethod();
|
|
||||||
$status = $response->getStatusCode();
|
|
||||||
|
|
||||||
// HEAD requests and `204 No Content` and `304 Not Modified` always use an empty response body
|
|
||||||
if ($method === 'HEAD' || $status === Response::STATUS_NO_CONTENT || $status === Response::STATUS_NOT_MODIFIED) {
|
|
||||||
$responseSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
\assert($this->logger instanceof LogStreamHandler);
|
|
||||||
$this->logger->log(
|
|
||||||
($request->getAttribute('remote_addr') ?? $request->getServerParams()['REMOTE_ADDR'] ?? '-') . ' ' .
|
|
||||||
'"' . $this->escape($method) . ' ' . $this->escape($request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . '" ' .
|
|
||||||
$status . ' ' . $responseSize . ' ' . sprintf('%.3F', $time < 0 ? 0 : $time)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function escape(string $s): string
|
|
||||||
{
|
|
||||||
return (string) preg_replace_callback('/[\x00-\x1F\x7F-\xFF"\\\\]+/', function (array $m) {
|
|
||||||
return str_replace('%', '\x', rawurlencode($m[0]));
|
|
||||||
}, $s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function now(): float
|
|
||||||
{
|
|
||||||
return $this->hasHighResolution ? hrtime(true) * 1e-9 : microtime(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
354
vendor/clue/framework-x/src/App.php
vendored
354
vendor/clue/framework-x/src/App.php
vendored
@@ -1,354 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX;
|
|
||||||
|
|
||||||
use FrameworkX\Io\MiddlewareHandler;
|
|
||||||
use FrameworkX\Io\ReactiveHandler;
|
|
||||||
use FrameworkX\Io\RedirectHandler;
|
|
||||||
use FrameworkX\Io\RouteHandler;
|
|
||||||
use FrameworkX\Io\SapiHandler;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
use React\Promise\Deferred;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
use function React\Async\await;
|
|
||||||
|
|
||||||
class App
|
|
||||||
{
|
|
||||||
/** @var MiddlewareHandler */
|
|
||||||
private $handler;
|
|
||||||
|
|
||||||
/** @var RouteHandler */
|
|
||||||
private $router;
|
|
||||||
|
|
||||||
/** @var ReactiveHandler|SapiHandler */
|
|
||||||
private $sapi;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate new X application
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* // instantiate
|
|
||||||
* $app = new App();
|
|
||||||
*
|
|
||||||
* // instantiate with global middleware
|
|
||||||
* $app = new App($middleware);
|
|
||||||
* $app = new App($middleware1, $middleware2);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param callable|class-string ...$middleware
|
|
||||||
*/
|
|
||||||
public function __construct(...$middleware)
|
|
||||||
{
|
|
||||||
// new MiddlewareHandler([$fiberHandler, $accessLogHandler, $errorHandler, ...$middleware, $routeHandler])
|
|
||||||
$handlers = [];
|
|
||||||
|
|
||||||
$container = $needsErrorHandler = new Container();
|
|
||||||
|
|
||||||
// only log for built-in webserver and PHP development webserver by default, others have their own access log
|
|
||||||
$needsAccessLog = (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server') ? $container : null;
|
|
||||||
|
|
||||||
if ($middleware) {
|
|
||||||
$needsErrorHandlerNext = false;
|
|
||||||
foreach ($middleware as $handler) {
|
|
||||||
// load AccessLogHandler and ErrorHandler instance from last Container
|
|
||||||
if ($handler === AccessLogHandler::class) {
|
|
||||||
$handler = $container->getAccessLogHandler();
|
|
||||||
} elseif ($handler === ErrorHandler::class) {
|
|
||||||
$handler = $container->getErrorHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure AccessLogHandler is always followed by ErrorHandler
|
|
||||||
if ($needsErrorHandlerNext && !$handler instanceof ErrorHandler) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$needsErrorHandlerNext = false;
|
|
||||||
|
|
||||||
if ($handler instanceof Container) {
|
|
||||||
// remember last Container to load any following class names
|
|
||||||
$container = $handler;
|
|
||||||
|
|
||||||
// add default ErrorHandler from last Container before adding any other handlers, may be followed by other Container instances (unlikely)
|
|
||||||
if (!$handlers) {
|
|
||||||
$needsErrorHandler = $needsAccessLog = $container;
|
|
||||||
}
|
|
||||||
} elseif (!\is_callable($handler)) {
|
|
||||||
$handlers[] = $container->callable($handler);
|
|
||||||
} else {
|
|
||||||
// don't need a default ErrorHandler if we're adding one as first handler or AccessLogHandler as first followed by one
|
|
||||||
if ($needsErrorHandler && ($handler instanceof ErrorHandler || $handler instanceof AccessLogHandler) && !$handlers) {
|
|
||||||
$needsErrorHandler = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only add to list of handlers if this is not a NOOP
|
|
||||||
if (!$handler instanceof AccessLogHandler || !$handler->isDevNull()) {
|
|
||||||
$handlers[] = $handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($handler instanceof AccessLogHandler) {
|
|
||||||
$needsAccessLog = null;
|
|
||||||
$needsErrorHandlerNext = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($needsErrorHandlerNext) {
|
|
||||||
throw new \TypeError('AccessLogHandler must be followed by ErrorHandler');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add default ErrorHandler as first handler unless it is already added explicitly
|
|
||||||
if ($needsErrorHandler instanceof Container) {
|
|
||||||
\array_unshift($handlers, $needsErrorHandler->getErrorHandler());
|
|
||||||
}
|
|
||||||
|
|
||||||
// only log for built-in webserver and PHP development webserver by default, others have their own access log
|
|
||||||
if ($needsAccessLog instanceof Container) {
|
|
||||||
$handler = $needsAccessLog->getAccessLogHandler();
|
|
||||||
if (!$handler->isDevNull()) {
|
|
||||||
\array_unshift($handlers, $handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->router = new RouteHandler($container);
|
|
||||||
$handlers[] = $this->router;
|
|
||||||
$this->handler = new MiddlewareHandler($handlers);
|
|
||||||
$this->sapi = \PHP_SAPI === 'cli' ? new ReactiveHandler($container->getEnv('X_LISTEN')) : new SapiHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function get(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->map(['GET'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function head(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->map(['HEAD'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function post(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->map(['POST'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function put(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->map(['PUT'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function patch(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->map(['PATCH'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function delete(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->map(['DELETE'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function options(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
// backward compatibility: `OPTIONS * HTTP/1.1` can be matched with empty path (legacy)
|
|
||||||
if ($route === '') {
|
|
||||||
$route = '*';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->map(['OPTIONS'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function any(string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param string[] $methods
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function map(array $methods, string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
$this->router->map($methods, $route, $handler, ...$handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $route
|
|
||||||
* @param string $target
|
|
||||||
* @param int $code
|
|
||||||
*/
|
|
||||||
public function redirect(string $route, string $target, int $code = Response::STATUS_FOUND): void
|
|
||||||
{
|
|
||||||
$this->any($route, new RedirectHandler($target, $code));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs the app to handle HTTP requests according to any registered routes and middleware.
|
|
||||||
*
|
|
||||||
* This is where the magic happens: When executed on the command line (CLI),
|
|
||||||
* this will run the powerful reactive request handler built on top of
|
|
||||||
* ReactPHP. This works by running the efficient built-in HTTP web server to
|
|
||||||
* handle incoming HTTP requests through ReactPHP's HTTP and socket server.
|
|
||||||
* This async execution mode is usually recommended as it can efficiently
|
|
||||||
* process a large number of concurrent connections and process multiple
|
|
||||||
* incoming requests simultaneously. The long-running server process will
|
|
||||||
* continue to run until it is interrupted by a signal.
|
|
||||||
*
|
|
||||||
* When executed behind traditional PHP SAPIs (PHP-FPM, FastCGI, Apache, etc.),
|
|
||||||
* this will handle a single request and run until a single response is sent.
|
|
||||||
* This is particularly useful because it allows you to run the exact same
|
|
||||||
* app in any environment.
|
|
||||||
*
|
|
||||||
* @see ReactiveHandler::run()
|
|
||||||
* @see SapiHandler::run()
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
$this->sapi->run(\Closure::fromCallable([$this, 'handleRequest']));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the app to handle a single HTTP request according to any registered routes and middleware.
|
|
||||||
*
|
|
||||||
* This method allows you to pass in a single HTTP request object that will
|
|
||||||
* be processed according to any registered routes and middleware and will
|
|
||||||
* return an HTTP response object as a result.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $app = new FrameworkX\App();
|
|
||||||
* $app->get('/', fn() => React\Http\Message\Response::plaintext("Hello!\n"));
|
|
||||||
*
|
|
||||||
* $request = new React\Http\Message\ServerRequest('GET', 'https://example.com/');
|
|
||||||
* $response = $app($request);
|
|
||||||
*
|
|
||||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
* assert($response->getStatusCode() === 200);
|
|
||||||
* assert($response->getBody()->getContents() === "Hello\n");
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This is particularly useful for higher-level integration test suites and
|
|
||||||
* for custom integrations with other runtime environments like serverless
|
|
||||||
* functions or other frameworks. Otherwise, most applications would likely
|
|
||||||
* want to use the `run()` method to run the application and automatically
|
|
||||||
* accept incoming HTTP requests according to the PHP SAPI in use.
|
|
||||||
*
|
|
||||||
* @param ServerRequestInterface $request The HTTP request object to process.
|
|
||||||
* @return ResponseInterface This method returns an HTTP response object
|
|
||||||
* according to any registered routes and middleware. If any handler is
|
|
||||||
* async, it will await its execution before returning, running the
|
|
||||||
* event loop as needed. If the request can not be routed or any handler
|
|
||||||
* fails, it will return a matching HTTP error response object.
|
|
||||||
* @throws void This method never throws. If the request can not be routed
|
|
||||||
* or any handler fails, it will be turned into a valid error response
|
|
||||||
* before returning.
|
|
||||||
* @see self::run()
|
|
||||||
*/
|
|
||||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
|
||||||
{
|
|
||||||
$response = $this->handleRequest($request);
|
|
||||||
if ($response instanceof PromiseInterface) {
|
|
||||||
/** @throws void */
|
|
||||||
$response = await($response);
|
|
||||||
assert($response instanceof ResponseInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ServerRequestInterface $request
|
|
||||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>
|
|
||||||
* Returns a response or a Promise which eventually fulfills with a
|
|
||||||
* response. This method never throws or resolves a rejected promise.
|
|
||||||
* If the request can not be routed or the handler fails, it will be
|
|
||||||
* turned into a valid error response before returning.
|
|
||||||
* @throws void
|
|
||||||
*/
|
|
||||||
private function handleRequest(ServerRequestInterface $request)
|
|
||||||
{
|
|
||||||
$response = ($this->handler)($request);
|
|
||||||
assert($response instanceof ResponseInterface || $response instanceof PromiseInterface || $response instanceof \Generator);
|
|
||||||
|
|
||||||
if ($response instanceof \Generator) {
|
|
||||||
if ($response->valid()) {
|
|
||||||
$response = $this->coroutine($response);
|
|
||||||
} else {
|
|
||||||
$response = $response->getReturn();
|
|
||||||
assert($response instanceof ResponseInterface);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return PromiseInterface<ResponseInterface>
|
|
||||||
*/
|
|
||||||
private function coroutine(\Generator $generator): PromiseInterface
|
|
||||||
{
|
|
||||||
$next = null;
|
|
||||||
$deferred = new Deferred();
|
|
||||||
$next = function () use ($generator, &$next, $deferred) {
|
|
||||||
if (!$generator->valid()) {
|
|
||||||
$deferred->resolve($generator->getReturn());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$promise = $generator->current();
|
|
||||||
assert($promise instanceof PromiseInterface);
|
|
||||||
|
|
||||||
$promise->then(function ($value) use ($generator, $next) {
|
|
||||||
$generator->send($value);
|
|
||||||
$next();
|
|
||||||
}, function ($reason) use ($generator, $next) {
|
|
||||||
$generator->throw($reason);
|
|
||||||
$next();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$next();
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
382
vendor/clue/framework-x/src/Container.php
vendored
382
vendor/clue/framework-x/src/Container.php
vendored
@@ -1,382 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX;
|
|
||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final
|
|
||||||
*/
|
|
||||||
class Container
|
|
||||||
{
|
|
||||||
/** @var array<string,object|callable():(object|scalar|null)|scalar|null>|ContainerInterface */
|
|
||||||
private $container;
|
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $useProcessEnv;
|
|
||||||
|
|
||||||
/** @param array<string,callable():(object|scalar|null) | object | scalar | null>|ContainerInterface $loader */
|
|
||||||
public function __construct($loader = [])
|
|
||||||
{
|
|
||||||
/** @var mixed $loader explicit type check for mixed if user ignores parameter type */
|
|
||||||
if (!\is_array($loader) && !$loader instanceof ContainerInterface) {
|
|
||||||
throw new \TypeError(
|
|
||||||
'Argument #1 ($loader) must be of type array|Psr\Container\ContainerInterface, ' . (\is_object($loader) ? get_class($loader) : gettype($loader)) . ' given'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (($loader instanceof ContainerInterface ? [] : $loader) as $name => $value) {
|
|
||||||
if (
|
|
||||||
(!\is_object($value) && !\is_scalar($value) && $value !== null) ||
|
|
||||||
(!$value instanceof $name && !$value instanceof \Closure && !\is_string($value) && \strpos($name, '\\') !== false)
|
|
||||||
) {
|
|
||||||
throw new \BadMethodCallException('Map for ' . $name . ' contains unexpected ' . (is_object($value) ? get_class($value) : gettype($value)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->container = $loader;
|
|
||||||
|
|
||||||
// prefer reading environment from `$_ENV` and `$_SERVER`, only fall back to `getenv()` in thread-safe environments
|
|
||||||
$this->useProcessEnv = \ZEND_THREAD_SAFE === false || \in_array(\PHP_SAPI, ['cli', 'cli-server', 'cgi-fcgi', 'fpm-fcgi'], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return mixed */
|
|
||||||
public function __invoke(ServerRequestInterface $request, callable $next = null)
|
|
||||||
{
|
|
||||||
if ($next === null) {
|
|
||||||
// You don't want to end up here. This only happens if you use the
|
|
||||||
// container as a final request handler instead of as a middleware.
|
|
||||||
// In this case, you should omit the container or add another final
|
|
||||||
// request handler behind the container in the middleware chain.
|
|
||||||
throw new \BadMethodCallException('Container should not be used as final request handler');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the container is used as a middleware, simply forward to the next
|
|
||||||
// request handler. As an additional optimization, the container would
|
|
||||||
// usually be filtered out from a middleware chain as this is a NO-OP.
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param class-string $class
|
|
||||||
* @return callable(ServerRequestInterface,?callable=null)
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function callable(string $class): callable
|
|
||||||
{
|
|
||||||
return function (ServerRequestInterface $request, callable $next = null) use ($class) {
|
|
||||||
// Check `$class` references a valid class name that can be autoloaded
|
|
||||||
if (\is_array($this->container) && !\class_exists($class, true) && !interface_exists($class, false) && !trait_exists($class, false)) {
|
|
||||||
throw new \BadMethodCallException('Request handler class ' . $class . ' not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($this->container instanceof ContainerInterface) {
|
|
||||||
$handler = $this->container->get($class);
|
|
||||||
} else {
|
|
||||||
$handler = $this->loadObject($class);
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
throw new \BadMethodCallException(
|
|
||||||
'Request handler class ' . $class . ' failed to load: ' . $e->getMessage(),
|
|
||||||
0,
|
|
||||||
$e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check `$handler` references a class name that is callable, i.e. has an `__invoke()` method.
|
|
||||||
// This initial version is intentionally limited to checking the method name only.
|
|
||||||
// A follow-up version will likely use reflection to check request handler argument types.
|
|
||||||
if (!is_callable($handler)) {
|
|
||||||
throw new \BadMethodCallException('Request handler class "' . $class . '" has no public __invoke() method');
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke request handler as middleware handler or final controller
|
|
||||||
if ($next === null) {
|
|
||||||
return $handler($request);
|
|
||||||
}
|
|
||||||
return $handler($request, $next);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
public function getEnv(string $name): ?string
|
|
||||||
{
|
|
||||||
assert(\preg_match('/^[A-Z][A-Z0-9_]+$/', $name) === 1);
|
|
||||||
|
|
||||||
if ($this->container instanceof ContainerInterface && $this->container->has($name)) {
|
|
||||||
$value = $this->container->get($name);
|
|
||||||
} elseif ($this->hasVariable($name)) {
|
|
||||||
$value = $this->loadVariable($name, 'mixed', true, 64);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!\is_string($value) && $value !== null) {
|
|
||||||
throw new \TypeError('Environment variable $' . $name . ' expected type string|null, but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
public function getAccessLogHandler(): AccessLogHandler
|
|
||||||
{
|
|
||||||
if ($this->container instanceof ContainerInterface) {
|
|
||||||
if ($this->container->has(AccessLogHandler::class)) {
|
|
||||||
// @phpstan-ignore-next-line method return type will ensure correct type or throw `TypeError`
|
|
||||||
return $this->container->get(AccessLogHandler::class);
|
|
||||||
} else {
|
|
||||||
return new AccessLogHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->loadObject(AccessLogHandler::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
public function getErrorHandler(): ErrorHandler
|
|
||||||
{
|
|
||||||
if ($this->container instanceof ContainerInterface) {
|
|
||||||
if ($this->container->has(ErrorHandler::class)) {
|
|
||||||
// @phpstan-ignore-next-line method return type will ensure correct type or throw `TypeError`
|
|
||||||
return $this->container->get(ErrorHandler::class);
|
|
||||||
} else {
|
|
||||||
return new ErrorHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->loadObject(ErrorHandler::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T of object
|
|
||||||
* @param class-string<T> $name
|
|
||||||
* @return T
|
|
||||||
* @throws \BadMethodCallException if object of type $name can not be loaded
|
|
||||||
*/
|
|
||||||
private function loadObject(string $name, int $depth = 64) /*: object (PHP 7.2+) */
|
|
||||||
{
|
|
||||||
assert(\is_array($this->container));
|
|
||||||
|
|
||||||
if (\array_key_exists($name, $this->container)) {
|
|
||||||
if (\is_string($this->container[$name])) {
|
|
||||||
if ($depth < 1) {
|
|
||||||
throw new \BadMethodCallException('Factory for ' . $name . ' is recursive');
|
|
||||||
}
|
|
||||||
|
|
||||||
// @phpstan-ignore-next-line because type of container value is explicitly checked after getting here
|
|
||||||
$value = $this->loadObject($this->container[$name], $depth - 1);
|
|
||||||
if (!$value instanceof $name) {
|
|
||||||
throw new \BadMethodCallException('Factory for ' . $name . ' returned unexpected ' . \get_class($value));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->container[$name] = $value;
|
|
||||||
} elseif ($this->container[$name] instanceof \Closure) {
|
|
||||||
// build list of factory parameters based on parameter types
|
|
||||||
$closure = new \ReflectionFunction($this->container[$name]);
|
|
||||||
$params = $this->loadFunctionParams($closure, $depth, true);
|
|
||||||
|
|
||||||
// invoke factory with list of parameters
|
|
||||||
$value = $params === [] ? ($this->container[$name])() : ($this->container[$name])(...$params);
|
|
||||||
|
|
||||||
if (\is_string($value)) {
|
|
||||||
if ($depth < 1) {
|
|
||||||
throw new \BadMethodCallException('Factory for ' . $name . ' is recursive');
|
|
||||||
}
|
|
||||||
|
|
||||||
// @phpstan-ignore-next-line because type of container value is explicitly checked after getting here
|
|
||||||
$value = $this->loadObject($value, $depth - 1);
|
|
||||||
}
|
|
||||||
if (!$value instanceof $name) {
|
|
||||||
throw new \BadMethodCallException('Factory for ' . $name . ' returned unexpected ' . (is_object($value) ? get_class($value) : gettype($value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->container[$name] = $value;
|
|
||||||
} elseif (!$this->container[$name] instanceof $name) {
|
|
||||||
throw new \BadMethodCallException('Map for ' . $name . ' contains unexpected ' . (\is_object($this->container[$name]) ? \get_class($this->container[$name]) : \gettype($this->container[$name])));
|
|
||||||
}
|
|
||||||
|
|
||||||
assert($this->container[$name] instanceof $name);
|
|
||||||
|
|
||||||
return $this->container[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check `$name` references a valid class name that can be autoloaded
|
|
||||||
if (!\class_exists($name, true) && !interface_exists($name, false) && !trait_exists($name, false)) {
|
|
||||||
throw new \BadMethodCallException('Class ' . $name . ' not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
$class = new \ReflectionClass($name);
|
|
||||||
if (!$class->isInstantiable()) {
|
|
||||||
$modifier = 'class';
|
|
||||||
if ($class->isInterface()) {
|
|
||||||
$modifier = 'interface';
|
|
||||||
} elseif ($class->isAbstract()) {
|
|
||||||
$modifier = 'abstract class';
|
|
||||||
} elseif ($class->isTrait()) {
|
|
||||||
$modifier = 'trait';
|
|
||||||
}
|
|
||||||
throw new \BadMethodCallException('Cannot instantiate ' . $modifier . ' '. $name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build list of constructor parameters based on parameter types
|
|
||||||
$ctor = $class->getConstructor();
|
|
||||||
$params = $ctor === null ? [] : $this->loadFunctionParams($ctor, $depth, false);
|
|
||||||
|
|
||||||
// instantiate with list of parameters
|
|
||||||
// @phpstan-ignore-next-line because `$class->newInstance()` is known to return `T`
|
|
||||||
return $this->container[$name] = $params === [] ? new $name() : $class->newInstance(...$params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return list<mixed>
|
|
||||||
* @throws \BadMethodCallException if either parameter can not be loaded
|
|
||||||
*/
|
|
||||||
private function loadFunctionParams(\ReflectionFunctionAbstract $function, int $depth, bool $allowVariables): array
|
|
||||||
{
|
|
||||||
$params = [];
|
|
||||||
foreach ($function->getParameters() as $parameter) {
|
|
||||||
$params[] = $this->loadParameter($parameter, $depth, $allowVariables);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $params;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
* @throws \BadMethodCallException if $parameter can not be loaded
|
|
||||||
*/
|
|
||||||
private function loadParameter(\ReflectionParameter $parameter, int $depth, bool $allowVariables) /*: mixed (PHP 8.0+) */
|
|
||||||
{
|
|
||||||
assert(\is_array($this->container));
|
|
||||||
|
|
||||||
$type = $parameter->getType();
|
|
||||||
$hasDefault = $parameter->isDefaultValueAvailable() || ((!$type instanceof \ReflectionNamedType || $type->getName() !== 'mixed') && $parameter->allowsNull());
|
|
||||||
|
|
||||||
// abort for union types (PHP 8.0+) and intersection types (PHP 8.1+)
|
|
||||||
// @phpstan-ignore-next-line for PHP < 8
|
|
||||||
if ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { // @codeCoverageIgnoreStart
|
|
||||||
if ($hasDefault) {
|
|
||||||
return $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
|
|
||||||
}
|
|
||||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' expects unsupported type ' . $type);
|
|
||||||
} // @codeCoverageIgnoreEnd
|
|
||||||
|
|
||||||
// load container variables if parameter name is known
|
|
||||||
assert($type === null || $type instanceof \ReflectionNamedType);
|
|
||||||
if ($allowVariables && $this->hasVariable($parameter->getName())) {
|
|
||||||
return $this->loadVariable($parameter->getName(), $type === null ? 'mixed' : $type->getName(), $parameter->allowsNull(), $depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// abort if parameter is untyped and not explicitly defined by container variable
|
|
||||||
if ($type === null) {
|
|
||||||
assert($parameter->allowsNull());
|
|
||||||
if ($parameter->isDefaultValueAvailable()) {
|
|
||||||
return $parameter->getDefaultValue();
|
|
||||||
}
|
|
||||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' has no type');
|
|
||||||
}
|
|
||||||
|
|
||||||
// use default/nullable argument if not loadable as container variable or by type
|
|
||||||
assert($type instanceof \ReflectionNamedType);
|
|
||||||
if ($hasDefault && ($type->isBuiltin() || !\array_key_exists($type->getName(), $this->container))) {
|
|
||||||
return $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// abort if required container variable is not defined or for any other primitive types (array etc.)
|
|
||||||
if ($type->isBuiltin()) {
|
|
||||||
if ($allowVariables) {
|
|
||||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' is not defined');
|
|
||||||
} else {
|
|
||||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' expects unsupported type ' . $type->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// abort for unreasonably deep nesting or recursive types
|
|
||||||
if ($depth < 1) {
|
|
||||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' is recursive');
|
|
||||||
}
|
|
||||||
|
|
||||||
// @phpstan-ignore-next-line because `$type->getName()` is a `class-string` by definition
|
|
||||||
return $this->loadObject($type->getName(), $depth - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function hasVariable(string $name): bool
|
|
||||||
{
|
|
||||||
return (\is_array($this->container) && \array_key_exists($name, $this->container)) || (isset($_ENV[$name]) || (\is_string($_SERVER[$name] ?? null) || ($this->useProcessEnv && \getenv($name) !== false)) && \preg_match('/^[A-Z][A-Z0-9_]+$/', $name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return object|string|int|float|bool|null
|
|
||||||
* @throws \BadMethodCallException if $name is not a valid container variable
|
|
||||||
*/
|
|
||||||
private function loadVariable(string $name, string $type, bool $nullable, int $depth) /*: object|string|int|float|bool|null (PHP 8.0+) */
|
|
||||||
{
|
|
||||||
assert($this->hasVariable($name));
|
|
||||||
assert(\is_array($this->container) || !$this->container->has($name));
|
|
||||||
|
|
||||||
if (\is_array($this->container) && ($this->container[$name] ?? null) instanceof \Closure) {
|
|
||||||
if ($depth < 1) {
|
|
||||||
throw new \BadMethodCallException('Container variable $' . $name . ' is recursive');
|
|
||||||
}
|
|
||||||
|
|
||||||
// build list of factory parameters based on parameter types
|
|
||||||
$factory = $this->container[$name];
|
|
||||||
assert($factory instanceof \Closure);
|
|
||||||
$closure = new \ReflectionFunction($factory);
|
|
||||||
$params = $this->loadFunctionParams($closure, $depth - 1, true);
|
|
||||||
|
|
||||||
// invoke factory with list of parameters
|
|
||||||
$value = $params === [] ? $factory() : $factory(...$params);
|
|
||||||
|
|
||||||
if (!\is_object($value) && !\is_scalar($value) && $value !== null) {
|
|
||||||
throw new \BadMethodCallException('Container variable $' . $name . ' expected type object|scalar|null from factory, but got ' . \gettype($value));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->container[$name] = $value;
|
|
||||||
} elseif (\is_array($this->container) && \array_key_exists($name, $this->container)) {
|
|
||||||
$value = $this->container[$name];
|
|
||||||
} elseif (isset($_ENV[$name])) {
|
|
||||||
assert(\is_string($_ENV[$name]));
|
|
||||||
$value = $_ENV[$name];
|
|
||||||
} elseif (isset($_SERVER[$name])) {
|
|
||||||
assert(\is_string($_SERVER[$name]));
|
|
||||||
$value = $_SERVER[$name];
|
|
||||||
} else {
|
|
||||||
$value = \getenv($name);
|
|
||||||
assert($this->useProcessEnv && $value !== false);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(\is_object($value) || \is_scalar($value) || $value === null);
|
|
||||||
|
|
||||||
// allow null values if parameter is marked nullable or untyped or mixed
|
|
||||||
if ($nullable && $value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip type checks and allow all values if expected type is undefined or mixed (PHP 8+)
|
|
||||||
if ($type === 'mixed') {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(\is_object($value) && !$value instanceof $type) ||
|
|
||||||
(!\is_object($value) && !\in_array($type, ['string', 'int', 'float', 'bool'])) ||
|
|
||||||
($type === 'string' && !\is_string($value)) || ($type === 'int' && !\is_int($value)) || ($type === 'float' && !\is_float($value)) || ($type === 'bool' && !\is_bool($value))
|
|
||||||
) {
|
|
||||||
throw new \BadMethodCallException('Container variable $' . $name . ' expected type ' . $type . ', but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @throws void */
|
|
||||||
private static function parameterError(\ReflectionParameter $parameter): string
|
|
||||||
{
|
|
||||||
$name = $parameter->getDeclaringFunction()->getShortName();
|
|
||||||
if (!$parameter->getDeclaringFunction()->isClosure() && ($class = $parameter->getDeclaringClass()) !== null) {
|
|
||||||
$name = explode("\0", $class->getName())[0] . '::' . $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Argument ' . ($parameter->getPosition() + 1) . ' ($' . $parameter->getName() . ') of ' . $name . '()';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
211
vendor/clue/framework-x/src/ErrorHandler.php
vendored
211
vendor/clue/framework-x/src/ErrorHandler.php
vendored
@@ -1,211 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX;
|
|
||||||
|
|
||||||
use FrameworkX\Io\HtmlHandler;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @final
|
|
||||||
*/
|
|
||||||
class ErrorHandler
|
|
||||||
{
|
|
||||||
/** @var Htmlhandler */
|
|
||||||
private $html;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->html = new HtmlHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
|
||||||
* Returns a response, a Promise which eventually fulfills with a
|
|
||||||
* response or a Generator which eventually returns a response. This
|
|
||||||
* method never throws or resolves a rejected promise. If the next
|
|
||||||
* handler fails to return a valid response, it will be turned into a
|
|
||||||
* valid error response before returning.
|
|
||||||
* @throws void
|
|
||||||
*/
|
|
||||||
public function __invoke(ServerRequestInterface $request, callable $next)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$response = $next($request);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return $this->errorInvalidException($e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($response instanceof ResponseInterface) {
|
|
||||||
return $response;
|
|
||||||
} elseif ($response instanceof PromiseInterface) {
|
|
||||||
return $response->then(function ($response) {
|
|
||||||
if ($response instanceof ResponseInterface) {
|
|
||||||
return $response;
|
|
||||||
} else {
|
|
||||||
return $this->errorInvalidResponse($response);
|
|
||||||
}
|
|
||||||
}, function ($e) {
|
|
||||||
// Promise rejected, always a `\Throwable` as of Promise v3
|
|
||||||
assert($e instanceof \Throwable || !\method_exists(PromiseInterface::class, 'catch')); // @phpstan-ignore-line
|
|
||||||
|
|
||||||
if ($e instanceof \Throwable) {
|
|
||||||
return $this->errorInvalidException($e);
|
|
||||||
} else { // @phpstan-ignore-line
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
return $this->errorInvalidResponse(\React\Promise\reject($e)); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} elseif ($response instanceof \Generator) {
|
|
||||||
return $this->coroutine($response);
|
|
||||||
} else {
|
|
||||||
return $this->errorInvalidResponse($response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function coroutine(\Generator $generator): \Generator
|
|
||||||
{
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
if (!$generator->valid()) {
|
|
||||||
$response = $generator->getReturn();
|
|
||||||
if ($response instanceof ResponseInterface) {
|
|
||||||
return $response;
|
|
||||||
} else {
|
|
||||||
return $this->errorInvalidResponse($response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return $this->errorInvalidException($e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$promise = $generator->current();
|
|
||||||
if (!$promise instanceof PromiseInterface) {
|
|
||||||
$gref = new \ReflectionGenerator($generator);
|
|
||||||
|
|
||||||
return $this->errorInvalidCoroutine(
|
|
||||||
$promise,
|
|
||||||
$gref->getExecutingFile(),
|
|
||||||
$gref->getExecutingLine()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$next = yield $promise;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
try {
|
|
||||||
$generator->throw($e);
|
|
||||||
continue;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return $this->errorInvalidException($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$generator->send($next);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return $this->errorInvalidException($e);
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
} // @codeCoverageIgnore
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
public function requestNotFound(): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->htmlResponse(
|
|
||||||
Response::STATUS_NOT_FOUND,
|
|
||||||
'Page Not Found',
|
|
||||||
'Please check the URL in the address bar and try again.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
* @param list<string> $allowedMethods
|
|
||||||
*/
|
|
||||||
public function requestMethodNotAllowed(array $allowedMethods): ResponseInterface
|
|
||||||
{
|
|
||||||
$methods = \implode('/', \array_map(function (string $method) { return '<code>' . $method . '</code>'; }, $allowedMethods));
|
|
||||||
|
|
||||||
return $this->htmlResponse(
|
|
||||||
Response::STATUS_METHOD_NOT_ALLOWED,
|
|
||||||
'Method Not Allowed',
|
|
||||||
'Please check the URL in the address bar and try again with ' . $methods . ' request.'
|
|
||||||
)->withHeader('Allow', \implode(', ', $allowedMethods));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
public function requestProxyUnsupported(): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->htmlResponse(
|
|
||||||
Response::STATUS_BAD_REQUEST,
|
|
||||||
'Proxy Requests Not Allowed',
|
|
||||||
'Please check your settings and retry.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function errorInvalidException(\Throwable $e): ResponseInterface
|
|
||||||
{
|
|
||||||
$where = ' in ' . $this->where($e->getFile(), $e->getLine());
|
|
||||||
$message = '<code>' . $this->html->escape($e->getMessage()) . '</code>';
|
|
||||||
|
|
||||||
return $this->htmlResponse(
|
|
||||||
Response::STATUS_INTERNAL_SERVER_ERROR,
|
|
||||||
'Internal Server Error',
|
|
||||||
'The requested page failed to load, please try again later.',
|
|
||||||
'Expected request handler to return <code>' . ResponseInterface::class . '</code> but got uncaught <code>' . \get_class($e) . '</code> with message ' . $message . $where . '.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param mixed $value */
|
|
||||||
private function errorInvalidResponse($value): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->htmlResponse(
|
|
||||||
Response::STATUS_INTERNAL_SERVER_ERROR,
|
|
||||||
'Internal Server Error',
|
|
||||||
'The requested page failed to load, please try again later.',
|
|
||||||
'Expected request handler to return <code>' . ResponseInterface::class . '</code> but got <code>' . $this->describeType($value) . '</code>.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param mixed $value */
|
|
||||||
private function errorInvalidCoroutine($value, string $file, int $line): ResponseInterface
|
|
||||||
{
|
|
||||||
$where = ' near or before '. $this->where($file, $line) . '.';
|
|
||||||
|
|
||||||
return $this->htmlResponse(
|
|
||||||
Response::STATUS_INTERNAL_SERVER_ERROR,
|
|
||||||
'Internal Server Error',
|
|
||||||
'The requested page failed to load, please try again later.',
|
|
||||||
'Expected request handler to yield <code>' . PromiseInterface::class . '</code> but got <code>' . $this->describeType($value) . '</code>' . $where
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function where(string $file, int $line): string
|
|
||||||
{
|
|
||||||
return '<code title="See ' . $file . ' line ' . $line . '">' . \basename($file) . ':' . $line . '</code>';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function htmlResponse(int $statusCode, string $title, string ...$info): ResponseInterface
|
|
||||||
{
|
|
||||||
return $this->html->statusResponse(
|
|
||||||
$statusCode,
|
|
||||||
'Error ' . $statusCode . ': ' .$title,
|
|
||||||
$title,
|
|
||||||
\implode('', \array_map(function (string $info) { return "<p>$info</p>\n"; }, $info))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param mixed $value */
|
|
||||||
private function describeType($value): string
|
|
||||||
{
|
|
||||||
if ($value === null) {
|
|
||||||
return 'null';
|
|
||||||
} elseif (\is_scalar($value) && !\is_string($value)) {
|
|
||||||
return \var_export($value, true);
|
|
||||||
}
|
|
||||||
return \is_object($value) ? \get_class($value) : \gettype($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
130
vendor/clue/framework-x/src/FilesystemHandler.php
vendored
130
vendor/clue/framework-x/src/FilesystemHandler.php
vendored
@@ -1,130 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX;
|
|
||||||
|
|
||||||
use FrameworkX\Io\HtmlHandler;
|
|
||||||
use FrameworkX\Io\RedirectHandler;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
|
|
||||||
class FilesystemHandler
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
private $root;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping between file extension and MIME type to send in `Content-Type` response header
|
|
||||||
*
|
|
||||||
* @var array<string,string>
|
|
||||||
*/
|
|
||||||
private $mimetypes = array(
|
|
||||||
'atom' => 'application/atom+xml',
|
|
||||||
'bz2' => 'application/x-bzip2',
|
|
||||||
'css' => 'text/css',
|
|
||||||
'gif' => 'image/gif',
|
|
||||||
'gz' => 'application/gzip',
|
|
||||||
'htm' => 'text/html',
|
|
||||||
'html' => 'text/html',
|
|
||||||
'ico' => 'image/x-icon',
|
|
||||||
'jpeg' => 'image/jpeg',
|
|
||||||
'jpg' => 'image/jpeg',
|
|
||||||
'js' => 'text/javascript',
|
|
||||||
'json' => 'application/json',
|
|
||||||
'pdf' => 'application/pdf',
|
|
||||||
'png' => 'image/png',
|
|
||||||
'rss' => 'application/rss+xml',
|
|
||||||
'svg' => 'image/svg+xml',
|
|
||||||
'tar' => 'application/x-tar',
|
|
||||||
'xml' => 'application/xml',
|
|
||||||
'zip' => 'application/zip',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign default MIME type to send in `Content-Type` response header (same as nginx/Apache)
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
* @see self::$mimetypes
|
|
||||||
*/
|
|
||||||
private $defaultMimetype = 'text/plain';
|
|
||||||
|
|
||||||
/** @var ErrorHandler */
|
|
||||||
private $errorHandler;
|
|
||||||
|
|
||||||
/** @var HtmlHandler */
|
|
||||||
private $html;
|
|
||||||
|
|
||||||
public function __construct(string $root)
|
|
||||||
{
|
|
||||||
$this->root = $root;
|
|
||||||
$this->errorHandler = new ErrorHandler();
|
|
||||||
$this->html = new HtmlHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
|
||||||
{
|
|
||||||
$local = $request->getAttribute('path', '');
|
|
||||||
assert(\is_string($local));
|
|
||||||
$path = \rtrim($this->root . '/' . $local, '/');
|
|
||||||
|
|
||||||
// local path should not contain "./", "../", "//" or null bytes or start with slash
|
|
||||||
$valid = !\preg_match('#(?:^|/)\.\.?(?:$|/)|^/|//|\x00#', $local);
|
|
||||||
|
|
||||||
\clearstatcache();
|
|
||||||
if ($valid && \is_dir($path)) {
|
|
||||||
if ($local !== '' && \substr($local, -1) !== '/') {
|
|
||||||
return (new RedirectHandler(\basename($path) . '/'))();
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = '<strong>' . $this->html->escape($local === '' ? '/' : $local) . '</strong>' . "\n<ul>\n";
|
|
||||||
|
|
||||||
if ($local !== '') {
|
|
||||||
$response .= ' <li><a href="../">../</a></li>' . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
$files = \scandir($path);
|
|
||||||
// @phpstan-ignore-next-line TODO handle error if directory can not be accessed
|
|
||||||
foreach ($files as $file) {
|
|
||||||
if ($file === '.' || $file === '..') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$dir = \is_dir($path . '/' . $file) ? '/' : '';
|
|
||||||
$response .= ' <li><a href="' . \rawurlencode($file) . $dir . '">' . $this->html->escape($file) . $dir . '</a></li>' . "\n";
|
|
||||||
}
|
|
||||||
$response .= '</ul>' . "\n";
|
|
||||||
|
|
||||||
return Response::html(
|
|
||||||
$response
|
|
||||||
);
|
|
||||||
} elseif ($valid && \is_file($path)) {
|
|
||||||
if ($local !== '' && \substr($local, -1) === '/') {
|
|
||||||
return (new RedirectHandler('../' . \basename($path)))();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign MIME type based on file extension (same as nginx/Apache) or fall back to given default otherwise.
|
|
||||||
// Browsers are pretty good at figuring out the correct type if no charset attribute is given.
|
|
||||||
$ext = \strtolower(\substr($path, \strrpos($path, '.') + 1));
|
|
||||||
$headers = [
|
|
||||||
'Content-Type' => $this->mimetypes[$ext] ?? $this->defaultMimetype
|
|
||||||
];
|
|
||||||
|
|
||||||
$stat = @\stat($path);
|
|
||||||
if ($stat !== false) {
|
|
||||||
$headers['Last-Modified'] = \gmdate('D, d M Y H:i:s', $stat['mtime']) . ' GMT';
|
|
||||||
|
|
||||||
if ($request->getHeaderLine('If-Modified-Since') === $headers['Last-Modified']) {
|
|
||||||
return new Response(Response::STATUS_NOT_MODIFIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
Response::STATUS_OK,
|
|
||||||
$headers,
|
|
||||||
\file_get_contents($path) // @phpstan-ignore-line TODO handle error if file can not be accessed
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return $this->errorHandler->requestNotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
67
vendor/clue/framework-x/src/Io/FiberHandler.php
vendored
67
vendor/clue/framework-x/src/Io/FiberHandler.php
vendored
@@ -1,67 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\Promise\Deferred;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Internal] Fibers middleware handler to ensure each request is processed in a separate `Fiber`
|
|
||||||
*
|
|
||||||
* The `Fiber` class has been added in PHP 8.1+, so this middleware is only used
|
|
||||||
* on PHP 8.1+. On supported PHP versions, this middleware is automatically
|
|
||||||
* added to the list of middleware handlers, so there's no need to reference
|
|
||||||
* this class in application code.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @link https://framework-x.org/docs/async/fibers/
|
|
||||||
*/
|
|
||||||
class FiberHandler
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
|
||||||
* Returns a `ResponseInterface` from the next request handler in the
|
|
||||||
* chain. If the next request handler returns immediately, this method
|
|
||||||
* will return immediately. If the next request handler suspends the
|
|
||||||
* fiber (see `await()`), this method will return a `PromiseInterface`
|
|
||||||
* that is fulfilled with a `ResponseInterface` when the fiber is
|
|
||||||
* terminated successfully. If the next request handler returns a
|
|
||||||
* promise, this method will return a promise that follows its
|
|
||||||
* resolution. If the next request handler returns a Generator-based
|
|
||||||
* coroutine, this method returns a `Generator`. This method never
|
|
||||||
* throws or resolves a rejected promise. If the handler fails, it will
|
|
||||||
* be turned into a valid error response before returning.
|
|
||||||
* @throws void
|
|
||||||
*/
|
|
||||||
public function __invoke(ServerRequestInterface $request, callable $next)
|
|
||||||
{
|
|
||||||
$deferred = null;
|
|
||||||
$fiber = new \Fiber(function () use ($request, $next, &$deferred) {
|
|
||||||
$response = $next($request);
|
|
||||||
assert($response instanceof ResponseInterface || $response instanceof PromiseInterface || $response instanceof \Generator);
|
|
||||||
|
|
||||||
// if the next request handler returns immediately, the fiber can terminate immediately without using a Deferred
|
|
||||||
// if the next request handler suspends the fiber, we only reach this point after resuming the fiber, so the code below will have assigned a Deferred
|
|
||||||
/** @var ?Deferred<ResponseInterface> $deferred */
|
|
||||||
if ($deferred !== null) {
|
|
||||||
assert($response instanceof ResponseInterface);
|
|
||||||
$deferred->resolve($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @throws void because the next handler will always be an `ErrorHandler` */
|
|
||||||
$fiber->start();
|
|
||||||
if ($fiber->isTerminated()) {
|
|
||||||
/** @throws void because fiber is known to have terminated successfully */
|
|
||||||
/** @var ResponseInterface|PromiseInterface<ResponseInterface>|\Generator */
|
|
||||||
return $fiber->getReturn();
|
|
||||||
}
|
|
||||||
|
|
||||||
$deferred = new Deferred();
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
65
vendor/clue/framework-x/src/Io/HtmlHandler.php
vendored
65
vendor/clue/framework-x/src/Io/HtmlHandler.php
vendored
@@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class HtmlHandler
|
|
||||||
{
|
|
||||||
public function statusResponse(int $statusCode, string $title, string $subtitle, string $info): ResponseInterface
|
|
||||||
{
|
|
||||||
$nonce = \base64_encode(\random_bytes(16));
|
|
||||||
$html = <<<HTML
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>$title</title>
|
|
||||||
<style nonce="$nonce">
|
|
||||||
body { display: grid; justify-content: center; align-items: center; grid-auto-rows: minmax(min-content, calc(100vh - 4em)); margin: 2em; font-family: ui-sans-serif, Arial, "Noto Sans", sans-serif; }
|
|
||||||
@media (min-width: 700px) { main { display: grid; max-width: 700px; } }
|
|
||||||
h1 { margin: 0 .5em 0 0; border-right: calc(2 * max(0px, min(100vw - 700px + 1px, 1px))) solid #e3e4e7; padding-right: .5em; color: #aebdcc; font-size: 3em; }
|
|
||||||
strong { color: #111827; font-size: 3em; }
|
|
||||||
p { margin: .5em 0 0 0; grid-column: 2; color: #6b7280; }
|
|
||||||
code { padding: 0 .3em; background-color: #f5f6f9; } code span { padding: 0 .2em; border-radius: 3px; background-color: #0001; }
|
|
||||||
a { color: inherit; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<h1>$statusCode</h1>
|
|
||||||
<strong>$subtitle</strong>
|
|
||||||
$info</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
HTML;
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
$statusCode,
|
|
||||||
[
|
|
||||||
'Content-Type' => 'text/html; charset=utf-8',
|
|
||||||
'Content-Security-Policy' => "style-src 'nonce-$nonce'; img-src 'self'; default-src 'none'"
|
|
||||||
],
|
|
||||||
$html
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function escape(string $s): string
|
|
||||||
{
|
|
||||||
return (string) \preg_replace_callback(
|
|
||||||
'/[\x00-\x1F]+/',
|
|
||||||
function (array $match): string {
|
|
||||||
return '<span>' . \addcslashes($match[0], "\x00..\xff") . '</span>';
|
|
||||||
},
|
|
||||||
(string) \preg_replace(
|
|
||||||
'/(^| ) |(?: $)/',
|
|
||||||
'$1 ',
|
|
||||||
\htmlspecialchars($s, \ENT_NOQUOTES | \ENT_SUBSTITUTE | \ENT_DISALLOWED, 'utf-8')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class LogStreamHandler
|
|
||||||
{
|
|
||||||
/** @var ?resource */
|
|
||||||
private $stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $path absolute log file path
|
|
||||||
* @throws \InvalidArgumentException if given `$path` is not an absolute file path
|
|
||||||
* @throws \RuntimeException if given `$path` can not be opened in append mode
|
|
||||||
*/
|
|
||||||
public function __construct(string $path)
|
|
||||||
{
|
|
||||||
if (\strpos($path, "\0") !== false || (\stripos($path, 'php://') !== 0 && !$this->isAbsolutePath($path))) {
|
|
||||||
throw new \InvalidArgumentException(
|
|
||||||
'Unable to open log file "' . \addslashes($path) . '": Invalid path given'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$errstr = '';
|
|
||||||
\set_error_handler(function (int $_, string $error) use (&$errstr): bool {
|
|
||||||
// Match errstr from PHP's warning message.
|
|
||||||
// fopen(/dev/not-a-valid-path): Failed to open stream: Permission denied
|
|
||||||
$errstr = \preg_replace('#.*: #', '', $error);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
$stream = \fopen($path, 'ae');
|
|
||||||
|
|
||||||
// try to fstat($stream) to see if this points to /dev/null (skip on Windows)
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
$stat = false;
|
|
||||||
if ($stream !== false && \DIRECTORY_SEPARATOR !== '\\') {
|
|
||||||
if (\strtolower($path) === 'php://output') {
|
|
||||||
// php://output doesn't support stat, so assume php://output will go to php://stdout
|
|
||||||
$stdout = \defined('STDOUT') ? \STDOUT : \fopen('php://stdout', 'w');
|
|
||||||
if (\is_resource($stdout)) {
|
|
||||||
$stat = \fstat($stdout);
|
|
||||||
} else {
|
|
||||||
// STDOUT can not be opened => assume piping to /dev/null
|
|
||||||
$stream = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$stat = \fstat($stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// close stream if it points to /dev/null
|
|
||||||
if ($stat !== false && $stat === \stat('/dev/null')) {
|
|
||||||
$stream = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
|
|
||||||
\restore_error_handler();
|
|
||||||
|
|
||||||
if ($stream === false) {
|
|
||||||
throw new \RuntimeException(
|
|
||||||
'Unable to open log file "' . $path . '": ' . $errstr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->stream = $stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isDevNull(): bool
|
|
||||||
{
|
|
||||||
return $this->stream === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function log(string $message): void
|
|
||||||
{
|
|
||||||
// nothing to do if we're writing to /dev/null
|
|
||||||
if ($this->stream === null) {
|
|
||||||
return; // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
|
|
||||||
$time = \microtime(true);
|
|
||||||
$prefix = \date('Y-m-d H:i:s', (int) $time) . \sprintf('.%03d ', (int) (($time - (int) $time) * 1e3));
|
|
||||||
|
|
||||||
$ret = \fwrite($this->stream, $prefix . $message . \PHP_EOL);
|
|
||||||
assert(\is_int($ret));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isAbsolutePath(string $path): bool
|
|
||||||
{
|
|
||||||
return \DIRECTORY_SEPARATOR !== '\\' ? \substr($path, 0, 1) === '/' : (bool) \preg_match('#^[A-Z]:[/\\\\]#i', $path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class MiddlewareHandler
|
|
||||||
{
|
|
||||||
/** @var list<callable> $handlers */
|
|
||||||
private $handlers;
|
|
||||||
|
|
||||||
/** @param list<callable> $handlers */
|
|
||||||
public function __construct(array $handlers)
|
|
||||||
{
|
|
||||||
assert(count($handlers) >= 2);
|
|
||||||
|
|
||||||
$this->handlers = $handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return mixed */
|
|
||||||
public function __invoke(ServerRequestInterface $request)
|
|
||||||
{
|
|
||||||
return $this->call($request, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return mixed */
|
|
||||||
private function call(ServerRequestInterface $request, int $position)
|
|
||||||
{
|
|
||||||
if (!isset($this->handlers[$position + 2])) {
|
|
||||||
return $this->handlers[$position]($request, $this->handlers[$position + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->handlers[$position]($request, function (ServerRequestInterface $request) use ($position) {
|
|
||||||
return $this->call($request, $position + 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
use React\EventLoop\Loop;
|
|
||||||
use React\Http\HttpServer;
|
|
||||||
use React\Socket\SocketServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Internal] Powerful reactive request handler built on top of ReactPHP.
|
|
||||||
*
|
|
||||||
* This is where the magic happens: The main `App` uses this class to run
|
|
||||||
* ReactPHP's efficient HTTP server to handle incoming HTTP requests when
|
|
||||||
* executed on the command line (CLI). ReactPHP's lightweight socket server can
|
|
||||||
* listen for a large number of concurrent connections and process multiple
|
|
||||||
* incoming connections simultaneously. The long-running server process will
|
|
||||||
* continue to run until it is interrupted by a signal.
|
|
||||||
*
|
|
||||||
* Note that this is an internal class only and nothing you should usually have
|
|
||||||
* to care about. See also the `App` and `SapiHandler` for more details.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class ReactiveHandler
|
|
||||||
{
|
|
||||||
/** @var LogStreamHandler */
|
|
||||||
private $logger;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
private $listenAddress;
|
|
||||||
|
|
||||||
public function __construct(?string $listenAddress)
|
|
||||||
{
|
|
||||||
/** @throws void */
|
|
||||||
$this->logger = new LogStreamHandler('php://output');
|
|
||||||
$this->listenAddress = $listenAddress ?? '127.0.0.1:8080';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function run(callable $handler): void
|
|
||||||
{
|
|
||||||
$socket = new SocketServer($this->listenAddress);
|
|
||||||
|
|
||||||
// create HTTP server, automatically start new fiber for each request on PHP 8.1+
|
|
||||||
$http = new HttpServer(...(\PHP_VERSION_ID >= 80100 ? [new FiberHandler(), $handler] : [$handler]));
|
|
||||||
$http->listen($socket);
|
|
||||||
|
|
||||||
$logger = $this->logger;
|
|
||||||
$logger->log('Listening on ' . \str_replace('tcp:', 'http:', (string) $socket->getAddress()));
|
|
||||||
|
|
||||||
$http->on('error', static function (\Exception $e) use ($logger): void {
|
|
||||||
$logger->log('HTTP error: ' . $e->getMessage());
|
|
||||||
});
|
|
||||||
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
try {
|
|
||||||
Loop::addSignal(\defined('SIGINT') ? \SIGINT : 2, $f1 = static function () use ($socket, $logger): void {
|
|
||||||
if (\PHP_VERSION_ID >= 70200 && \stream_isatty(\STDIN)) {
|
|
||||||
echo "\r";
|
|
||||||
}
|
|
||||||
$logger->log('Received SIGINT, stopping loop');
|
|
||||||
|
|
||||||
$socket->close();
|
|
||||||
Loop::stop();
|
|
||||||
});
|
|
||||||
Loop::addSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 = static function () use ($socket, $logger): void {
|
|
||||||
$logger->log('Received SIGTERM, stopping loop');
|
|
||||||
|
|
||||||
$socket->close();
|
|
||||||
Loop::stop();
|
|
||||||
});
|
|
||||||
} catch (\BadMethodCallException $e) {
|
|
||||||
$logger->log('Notice: No signal handler support, installing ext-ev or ext-pcntl recommended for production use.');
|
|
||||||
}
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
|
|
||||||
do {
|
|
||||||
Loop::run();
|
|
||||||
|
|
||||||
if ($socket->getAddress() !== null) {
|
|
||||||
// Fiber compatibility mode for PHP < 8.1: Restart loop as long as socket is available
|
|
||||||
$logger->log('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.');
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
|
|
||||||
// remove signal handlers when loop stops (if registered)
|
|
||||||
Loop::removeSignal(\defined('SIGINT') ? \SIGINT : 2, $f1 ?? 'printf');
|
|
||||||
Loop::removeSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 ?? 'printf');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class RedirectHandler
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
private $target;
|
|
||||||
|
|
||||||
/** @var int */
|
|
||||||
private $code;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
private $reason;
|
|
||||||
|
|
||||||
/** @var HtmlHandler */
|
|
||||||
private $html;
|
|
||||||
|
|
||||||
public function __construct(string $target, int $redirectStatusCode = Response::STATUS_FOUND)
|
|
||||||
{
|
|
||||||
if ($redirectStatusCode < 300 || $redirectStatusCode === Response::STATUS_NOT_MODIFIED || $redirectStatusCode >= 400) {
|
|
||||||
throw new \InvalidArgumentException('Invalid redirect status code given');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->target = $target;
|
|
||||||
$this->code = $redirectStatusCode;
|
|
||||||
$this->reason = \ucwords((new Response($redirectStatusCode))->getReasonPhrase()) ?: 'Redirect';
|
|
||||||
$this->html = new HtmlHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __invoke(): ResponseInterface
|
|
||||||
{
|
|
||||||
$url = $this->html->escape($this->target);
|
|
||||||
|
|
||||||
return $this->html->statusResponse(
|
|
||||||
$this->code,
|
|
||||||
'Redirecting to ' . $url,
|
|
||||||
$this->reason,
|
|
||||||
"<p>Redirecting to <a href=\"$url\"><code>$url</code></a>...</p>\n"
|
|
||||||
)->withHeader('Location', $this->target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
116
vendor/clue/framework-x/src/Io/RouteHandler.php
vendored
116
vendor/clue/framework-x/src/Io/RouteHandler.php
vendored
@@ -1,116 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
use FastRoute\DataGenerator\GroupCountBased as RouteGenerator;
|
|
||||||
use FastRoute\Dispatcher\GroupCountBased as RouteDispatcher;
|
|
||||||
use FastRoute\RouteCollector;
|
|
||||||
use FastRoute\RouteParser\Std as RouteParser;
|
|
||||||
use FrameworkX\AccessLogHandler;
|
|
||||||
use FrameworkX\Container;
|
|
||||||
use FrameworkX\ErrorHandler;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class RouteHandler
|
|
||||||
{
|
|
||||||
/** @var RouteCollector */
|
|
||||||
private $routeCollector;
|
|
||||||
|
|
||||||
/** @var ?RouteDispatcher */
|
|
||||||
private $routeDispatcher;
|
|
||||||
|
|
||||||
/** @var ErrorHandler */
|
|
||||||
private $errorHandler;
|
|
||||||
|
|
||||||
/** @var Container */
|
|
||||||
private $container;
|
|
||||||
|
|
||||||
public function __construct(Container $container = null)
|
|
||||||
{
|
|
||||||
$this->routeCollector = new RouteCollector(new RouteParser(), new RouteGenerator());
|
|
||||||
$this->errorHandler = new ErrorHandler();
|
|
||||||
$this->container = $container ?? new Container();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string[] $methods
|
|
||||||
* @param string $route
|
|
||||||
* @param callable|class-string $handler
|
|
||||||
* @param callable|class-string ...$handlers
|
|
||||||
*/
|
|
||||||
public function map(array $methods, string $route, $handler, ...$handlers): void
|
|
||||||
{
|
|
||||||
if ($handlers) {
|
|
||||||
\array_unshift($handlers, $handler);
|
|
||||||
\end($handlers);
|
|
||||||
} else {
|
|
||||||
$handlers = [$handler];
|
|
||||||
}
|
|
||||||
|
|
||||||
$last = key($handlers);
|
|
||||||
$container = $this->container;
|
|
||||||
foreach ($handlers as $i => $handler) {
|
|
||||||
if ($handler instanceof Container && $i !== $last) {
|
|
||||||
$container = $handler;
|
|
||||||
unset($handlers[$i]);
|
|
||||||
} elseif ($handler instanceof AccessLogHandler || $handler === AccessLogHandler::class) {
|
|
||||||
throw new \TypeError('AccessLogHandler may currently only be passed as a global middleware');
|
|
||||||
} elseif (!\is_callable($handler)) {
|
|
||||||
$handlers[$i] = $container->callable($handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var non-empty-array<callable> $handlers */
|
|
||||||
$handler = \count($handlers) > 1 ? new MiddlewareHandler(array_values($handlers)) : \reset($handlers);
|
|
||||||
$this->routeDispatcher = null;
|
|
||||||
$this->routeCollector->addRoute($methods, $route, $handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
|
||||||
*/
|
|
||||||
public function __invoke(ServerRequestInterface $request)
|
|
||||||
{
|
|
||||||
$target = $request->getRequestTarget();
|
|
||||||
if ($target[0] !== '/' && $target !== '*') {
|
|
||||||
return $this->errorHandler->requestProxyUnsupported();
|
|
||||||
} elseif ($target !== '*') {
|
|
||||||
$target = $request->getUri()->getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->routeDispatcher === null) {
|
|
||||||
$this->routeDispatcher = new RouteDispatcher($this->routeCollector->getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
$routeInfo = $this->routeDispatcher->dispatch($request->getMethod(), $target);
|
|
||||||
assert(\is_array($routeInfo) && isset($routeInfo[0]));
|
|
||||||
|
|
||||||
// happy path: matching route found, assign route attributes and invoke request handler
|
|
||||||
if ($routeInfo[0] === \FastRoute\Dispatcher::FOUND) {
|
|
||||||
$handler = $routeInfo[1];
|
|
||||||
$vars = $routeInfo[2];
|
|
||||||
|
|
||||||
foreach ($vars as $key => $value) {
|
|
||||||
$request = $request->withAttribute($key, rawurldecode($value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $handler($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no matching route found: report error `404 Not Found`
|
|
||||||
if ($routeInfo[0] === \FastRoute\Dispatcher::NOT_FOUND) {
|
|
||||||
return $this->errorHandler->requestNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
// unexpected request method for route: report error `405 Method Not Allowed`
|
|
||||||
assert($routeInfo[0] === \FastRoute\Dispatcher::METHOD_NOT_ALLOWED);
|
|
||||||
assert(\is_array($routeInfo[1]) && \count($routeInfo[1]) > 0);
|
|
||||||
|
|
||||||
return $this->errorHandler->requestMethodNotAllowed($routeInfo[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
164
vendor/clue/framework-x/src/Io/SapiHandler.php
vendored
164
vendor/clue/framework-x/src/Io/SapiHandler.php
vendored
@@ -1,164 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FrameworkX\Io;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\EventLoop\Loop;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
use React\Http\Message\ServerRequest;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
use React\Stream\ReadableStreamInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Internal] Request handler for traditional PHP SAPIs.
|
|
||||||
*
|
|
||||||
* This request handler will be used when executed behind traditional PHP SAPIs
|
|
||||||
* (PHP-FPM, FastCGI, Apache, etc.). It will handle a single request and run
|
|
||||||
* until a single response is sent. This is particularly useful because it
|
|
||||||
* allows you to run the exact same app in any environment.
|
|
||||||
*
|
|
||||||
* Note that this is an internal class only and nothing you should usually have
|
|
||||||
* to care about. See also the `App` and `ReactiveHandler` for more details.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
class SapiHandler
|
|
||||||
{
|
|
||||||
public function run(callable $handler): void
|
|
||||||
{
|
|
||||||
$request = $this->requestFromGlobals();
|
|
||||||
|
|
||||||
$response = $handler($request);
|
|
||||||
|
|
||||||
if ($response instanceof ResponseInterface) {
|
|
||||||
$this->sendResponse($response);
|
|
||||||
} elseif ($response instanceof PromiseInterface) {
|
|
||||||
/** @var PromiseInterface<ResponseInterface> $response */
|
|
||||||
$response->then(function (ResponseInterface $response): void {
|
|
||||||
$this->sendResponse($response);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Loop::run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function requestFromGlobals(): ServerRequestInterface
|
|
||||||
{
|
|
||||||
$host = null;
|
|
||||||
$headers = array();
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
if (\function_exists('getallheaders')) {
|
|
||||||
$headers = \getallheaders();
|
|
||||||
$host = \array_change_key_case($headers, \CASE_LOWER)['host'] ?? null;
|
|
||||||
} else {
|
|
||||||
foreach ($_SERVER as $key => $value) {
|
|
||||||
if (\strpos($key, 'HTTP_') === 0) {
|
|
||||||
$key = str_replace(' ', '-', \ucwords(\strtolower(\str_replace('_', ' ', \substr($key, 5)))));
|
|
||||||
$headers[$key] = $value;
|
|
||||||
|
|
||||||
if ($host === null && $key === 'Host') {
|
|
||||||
$host = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
|
|
||||||
$target = ($_SERVER['REQUEST_URI'] ?? '/');
|
|
||||||
$url = $target;
|
|
||||||
if (($target[0] ?? '/') === '/' || $target === '*') {
|
|
||||||
$url = (($_SERVER['HTTPS'] ?? null) === 'on' ? 'https://' : 'http://') . ($host ?? 'localhost') . ($target === '*' ? '' : $target);
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = file_get_contents('php://input');
|
|
||||||
assert(\is_string($body));
|
|
||||||
|
|
||||||
$request = new ServerRequest(
|
|
||||||
$_SERVER['REQUEST_METHOD'] ?? 'GET',
|
|
||||||
$url,
|
|
||||||
$headers,
|
|
||||||
$body,
|
|
||||||
substr($_SERVER['SERVER_PROTOCOL'] ?? 'http/1.1', 5),
|
|
||||||
$_SERVER
|
|
||||||
);
|
|
||||||
if ($host === null) {
|
|
||||||
$request = $request->withoutHeader('Host');
|
|
||||||
}
|
|
||||||
if (isset($target[0]) && $target[0] !== '/') {
|
|
||||||
$request = $request->withRequestTarget($target);
|
|
||||||
}
|
|
||||||
$request = $request->withParsedBody($_POST);
|
|
||||||
|
|
||||||
// Content-Length / Content-Type are special <3
|
|
||||||
if ($request->getHeaderLine('Content-Length') === '') {
|
|
||||||
$request = $request->withoutHeader('Content-Length');
|
|
||||||
}
|
|
||||||
if ($request->getHeaderLine('Content-Type') === '' && !isset($_SERVER['HTTP_CONTENT_TYPE'])) {
|
|
||||||
$request = $request->withoutHeader('Content-Type');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ResponseInterface $response
|
|
||||||
*/
|
|
||||||
public function sendResponse(ResponseInterface $response): void
|
|
||||||
{
|
|
||||||
$status = $response->getStatusCode();
|
|
||||||
$body = $response->getBody();
|
|
||||||
|
|
||||||
if ($status === Response::STATUS_NO_CONTENT) {
|
|
||||||
// `204 No Content` MUST NOT include "Content-Length" response header
|
|
||||||
$response = $response->withoutHeader('Content-Length');
|
|
||||||
} elseif (!$response->hasHeader('Content-Length') && $body->getSize() !== null && ($status !== Response::STATUS_NOT_MODIFIED || $body->getSize() !== 0)) {
|
|
||||||
// automatically assign "Content-Length" response header if known and not already present
|
|
||||||
$response = $response->withHeader('Content-Length', (string) $body->getSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove default "Content-Type" header set by PHP (default_mimetype)
|
|
||||||
if (!$response->hasHeader('Content-Type')) {
|
|
||||||
header('Content-Type:');
|
|
||||||
header_remove('Content-Type');
|
|
||||||
}
|
|
||||||
|
|
||||||
// send all headers without applying default "; charset=utf-8" set by PHP (default_charset)
|
|
||||||
$old = ini_get('default_charset');
|
|
||||||
assert(\is_string($old));
|
|
||||||
ini_set('default_charset', '');
|
|
||||||
foreach ($response->getHeaders() as $name => $values) {
|
|
||||||
foreach ($values as $value) {
|
|
||||||
header($name . ': ' . $value, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ini_set('default_charset', $old);
|
|
||||||
|
|
||||||
header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status . ' ' . $response->getReasonPhrase());
|
|
||||||
|
|
||||||
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'HEAD' || $status === Response::STATUS_NO_CONTENT || $status === Response::STATUS_NOT_MODIFIED) {
|
|
||||||
$body->close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($body instanceof ReadableStreamInterface) {
|
|
||||||
// try to disable nginx buffering (http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering)
|
|
||||||
if (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') === 0) {
|
|
||||||
header('X-Accel-Buffering: no');
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear output buffer to show streaming output (default in cli-server)
|
|
||||||
if (\PHP_SAPI === 'cli-server') {
|
|
||||||
\ob_end_flush(); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush data whenever stream reports one data chunk
|
|
||||||
$body->on('data', function ($chunk) {
|
|
||||||
echo $chunk;
|
|
||||||
flush();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
echo $body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
vendor/fig/http-message-util/.gitignore
vendored
1
vendor/fig/http-message-util/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
vendor/
|
|
||||||
147
vendor/fig/http-message-util/CHANGELOG.md
vendored
147
vendor/fig/http-message-util/CHANGELOG.md
vendored
@@ -1,147 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file, in reverse chronological order by release.
|
|
||||||
|
|
||||||
## 1.1.5 - 2020-11-24
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- [#19](https://github.com/php-fig/http-message-util/pull/19) adds support for PHP 8.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
## 1.1.4 - 2020-02-05
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- [#15](https://github.com/php-fig/http-message-util/pull/15) removes the dependency on psr/http-message, as it is not technically necessary for usage of this package.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
## 1.1.3 - 2018-11-19
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- [#10](https://github.com/php-fig/http-message-util/pull/10) adds the constants `StatusCodeInterface::STATUS_EARLY_HINTS` (103) and
|
|
||||||
`StatusCodeInterface::STATUS_TOO_EARLY` (425).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
## 1.1.2 - 2017-02-09
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- [#4](https://github.com/php-fig/http-message-util/pull/4) adds the constant
|
|
||||||
`StatusCodeInterface::STATUS_MISDIRECTED_REQUEST` (421).
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
## 1.1.1 - 2017-02-06
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- [#3](https://github.com/php-fig/http-message-util/pull/3) adds the constant
|
|
||||||
`StatusCodeInterface::STATUS_IM_A_TEAPOT` (418).
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
## 1.1.0 - 2016-09-19
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- [#1](https://github.com/php-fig/http-message-util/pull/1) adds
|
|
||||||
`Fig\Http\Message\StatusCodeInterface`, with constants named after common
|
|
||||||
status reason phrases, with values indicating the status codes themselves.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
## 1.0.0 - 2017-08-05
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Adds `Fig\Http\Message\RequestMethodInterface`, with constants covering the
|
|
||||||
most common HTTP request methods as specified by the IETF.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
19
vendor/fig/http-message-util/LICENSE
vendored
19
vendor/fig/http-message-util/LICENSE
vendored
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2016 PHP Framework Interoperability Group
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
17
vendor/fig/http-message-util/README.md
vendored
17
vendor/fig/http-message-util/README.md
vendored
@@ -1,17 +0,0 @@
|
|||||||
# PSR Http Message Util
|
|
||||||
|
|
||||||
This repository holds utility classes and constants to facilitate common
|
|
||||||
operations of [PSR-7](https://www.php-fig.org/psr/psr-7/); the primary purpose is
|
|
||||||
to provide constants for referring to request methods, response status codes and
|
|
||||||
messages, and potentially common headers.
|
|
||||||
|
|
||||||
Implementation of PSR-7 interfaces is **not** within the scope of this package.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Install by adding the package as a [Composer](https://getcomposer.org)
|
|
||||||
requirement:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ composer require fig/http-message-util
|
|
||||||
```
|
|
||||||
28
vendor/fig/http-message-util/composer.json
vendored
28
vendor/fig/http-message-util/composer.json
vendored
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "fig/http-message-util",
|
|
||||||
"description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
|
|
||||||
"keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "PHP-FIG",
|
|
||||||
"homepage": "https://www.php-fig.org/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": "^5.3 || ^7.0 || ^8.0"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"psr/http-message": "The package containing the PSR-7 interfaces"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Fig\\Http\\Message\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.1.x-dev"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Fig\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines constants for common HTTP request methods.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
*
|
|
||||||
* <code>
|
|
||||||
* class RequestFactory implements RequestMethodInterface
|
|
||||||
* {
|
|
||||||
* public static function factory(
|
|
||||||
* $uri = '/',
|
|
||||||
* $method = self::METHOD_GET,
|
|
||||||
* $data = []
|
|
||||||
* ) {
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </code>
|
|
||||||
*/
|
|
||||||
interface RequestMethodInterface
|
|
||||||
{
|
|
||||||
const METHOD_HEAD = 'HEAD';
|
|
||||||
const METHOD_GET = 'GET';
|
|
||||||
const METHOD_POST = 'POST';
|
|
||||||
const METHOD_PUT = 'PUT';
|
|
||||||
const METHOD_PATCH = 'PATCH';
|
|
||||||
const METHOD_DELETE = 'DELETE';
|
|
||||||
const METHOD_PURGE = 'PURGE';
|
|
||||||
const METHOD_OPTIONS = 'OPTIONS';
|
|
||||||
const METHOD_TRACE = 'TRACE';
|
|
||||||
const METHOD_CONNECT = 'CONNECT';
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Fig\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines constants for common HTTP status code.
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc2295#section-8.1
|
|
||||||
* @see https://tools.ietf.org/html/rfc2324#section-2.3
|
|
||||||
* @see https://tools.ietf.org/html/rfc2518#section-9.7
|
|
||||||
* @see https://tools.ietf.org/html/rfc2774#section-7
|
|
||||||
* @see https://tools.ietf.org/html/rfc3229#section-10.4
|
|
||||||
* @see https://tools.ietf.org/html/rfc4918#section-11
|
|
||||||
* @see https://tools.ietf.org/html/rfc5842#section-7.1
|
|
||||||
* @see https://tools.ietf.org/html/rfc5842#section-7.2
|
|
||||||
* @see https://tools.ietf.org/html/rfc6585#section-3
|
|
||||||
* @see https://tools.ietf.org/html/rfc6585#section-4
|
|
||||||
* @see https://tools.ietf.org/html/rfc6585#section-5
|
|
||||||
* @see https://tools.ietf.org/html/rfc6585#section-6
|
|
||||||
* @see https://tools.ietf.org/html/rfc7231#section-6
|
|
||||||
* @see https://tools.ietf.org/html/rfc7238#section-3
|
|
||||||
* @see https://tools.ietf.org/html/rfc7725#section-3
|
|
||||||
* @see https://tools.ietf.org/html/rfc7540#section-9.1.2
|
|
||||||
* @see https://tools.ietf.org/html/rfc8297#section-2
|
|
||||||
* @see https://tools.ietf.org/html/rfc8470#section-7
|
|
||||||
* Usage:
|
|
||||||
*
|
|
||||||
* <code>
|
|
||||||
* class ResponseFactory implements StatusCodeInterface
|
|
||||||
* {
|
|
||||||
* public function createResponse($code = self::STATUS_OK)
|
|
||||||
* {
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </code>
|
|
||||||
*/
|
|
||||||
interface StatusCodeInterface
|
|
||||||
{
|
|
||||||
// Informational 1xx
|
|
||||||
const STATUS_CONTINUE = 100;
|
|
||||||
const STATUS_SWITCHING_PROTOCOLS = 101;
|
|
||||||
const STATUS_PROCESSING = 102;
|
|
||||||
const STATUS_EARLY_HINTS = 103;
|
|
||||||
// Successful 2xx
|
|
||||||
const STATUS_OK = 200;
|
|
||||||
const STATUS_CREATED = 201;
|
|
||||||
const STATUS_ACCEPTED = 202;
|
|
||||||
const STATUS_NON_AUTHORITATIVE_INFORMATION = 203;
|
|
||||||
const STATUS_NO_CONTENT = 204;
|
|
||||||
const STATUS_RESET_CONTENT = 205;
|
|
||||||
const STATUS_PARTIAL_CONTENT = 206;
|
|
||||||
const STATUS_MULTI_STATUS = 207;
|
|
||||||
const STATUS_ALREADY_REPORTED = 208;
|
|
||||||
const STATUS_IM_USED = 226;
|
|
||||||
// Redirection 3xx
|
|
||||||
const STATUS_MULTIPLE_CHOICES = 300;
|
|
||||||
const STATUS_MOVED_PERMANENTLY = 301;
|
|
||||||
const STATUS_FOUND = 302;
|
|
||||||
const STATUS_SEE_OTHER = 303;
|
|
||||||
const STATUS_NOT_MODIFIED = 304;
|
|
||||||
const STATUS_USE_PROXY = 305;
|
|
||||||
const STATUS_RESERVED = 306;
|
|
||||||
const STATUS_TEMPORARY_REDIRECT = 307;
|
|
||||||
const STATUS_PERMANENT_REDIRECT = 308;
|
|
||||||
// Client Errors 4xx
|
|
||||||
const STATUS_BAD_REQUEST = 400;
|
|
||||||
const STATUS_UNAUTHORIZED = 401;
|
|
||||||
const STATUS_PAYMENT_REQUIRED = 402;
|
|
||||||
const STATUS_FORBIDDEN = 403;
|
|
||||||
const STATUS_NOT_FOUND = 404;
|
|
||||||
const STATUS_METHOD_NOT_ALLOWED = 405;
|
|
||||||
const STATUS_NOT_ACCEPTABLE = 406;
|
|
||||||
const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407;
|
|
||||||
const STATUS_REQUEST_TIMEOUT = 408;
|
|
||||||
const STATUS_CONFLICT = 409;
|
|
||||||
const STATUS_GONE = 410;
|
|
||||||
const STATUS_LENGTH_REQUIRED = 411;
|
|
||||||
const STATUS_PRECONDITION_FAILED = 412;
|
|
||||||
const STATUS_PAYLOAD_TOO_LARGE = 413;
|
|
||||||
const STATUS_URI_TOO_LONG = 414;
|
|
||||||
const STATUS_UNSUPPORTED_MEDIA_TYPE = 415;
|
|
||||||
const STATUS_RANGE_NOT_SATISFIABLE = 416;
|
|
||||||
const STATUS_EXPECTATION_FAILED = 417;
|
|
||||||
const STATUS_IM_A_TEAPOT = 418;
|
|
||||||
const STATUS_MISDIRECTED_REQUEST = 421;
|
|
||||||
const STATUS_UNPROCESSABLE_ENTITY = 422;
|
|
||||||
const STATUS_LOCKED = 423;
|
|
||||||
const STATUS_FAILED_DEPENDENCY = 424;
|
|
||||||
const STATUS_TOO_EARLY = 425;
|
|
||||||
const STATUS_UPGRADE_REQUIRED = 426;
|
|
||||||
const STATUS_PRECONDITION_REQUIRED = 428;
|
|
||||||
const STATUS_TOO_MANY_REQUESTS = 429;
|
|
||||||
const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
|
|
||||||
const STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
|
|
||||||
// Server Errors 5xx
|
|
||||||
const STATUS_INTERNAL_SERVER_ERROR = 500;
|
|
||||||
const STATUS_NOT_IMPLEMENTED = 501;
|
|
||||||
const STATUS_BAD_GATEWAY = 502;
|
|
||||||
const STATUS_SERVICE_UNAVAILABLE = 503;
|
|
||||||
const STATUS_GATEWAY_TIMEOUT = 504;
|
|
||||||
const STATUS_VERSION_NOT_SUPPORTED = 505;
|
|
||||||
const STATUS_VARIANT_ALSO_NEGOTIATES = 506;
|
|
||||||
const STATUS_INSUFFICIENT_STORAGE = 507;
|
|
||||||
const STATUS_LOOP_DETECTED = 508;
|
|
||||||
const STATUS_NOT_EXTENDED = 510;
|
|
||||||
const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511;
|
|
||||||
}
|
|
||||||
5
vendor/nikic/fast-route/.gitignore
vendored
5
vendor/nikic/fast-route/.gitignore
vendored
@@ -1,5 +0,0 @@
|
|||||||
/vendor/
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# ignore lock file since we have no extra dependencies
|
|
||||||
composer.lock
|
|
||||||
1
vendor/nikic/fast-route/.hhconfig
vendored
1
vendor/nikic/fast-route/.hhconfig
vendored
@@ -1 +0,0 @@
|
|||||||
assume_php=false
|
|
||||||
20
vendor/nikic/fast-route/.travis.yml
vendored
20
vendor/nikic/fast-route/.travis.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: php
|
|
||||||
|
|
||||||
php:
|
|
||||||
- 5.4
|
|
||||||
- 5.5
|
|
||||||
- 5.6
|
|
||||||
- 7.0
|
|
||||||
- 7.1
|
|
||||||
- 7.2
|
|
||||||
- hhvm
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./vendor/bin/phpunit
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- travis_retry composer self-update
|
|
||||||
|
|
||||||
install:
|
|
||||||
- composer install
|
|
||||||
126
vendor/nikic/fast-route/FastRoute.hhi
vendored
126
vendor/nikic/fast-route/FastRoute.hhi
vendored
@@ -1,126 +0,0 @@
|
|||||||
<?hh // decl
|
|
||||||
|
|
||||||
namespace FastRoute {
|
|
||||||
class BadRouteException extends \LogicException {
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RouteParser {
|
|
||||||
public function parse(string $route): array<array>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RouteCollector {
|
|
||||||
public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator);
|
|
||||||
public function addRoute(mixed $httpMethod, string $route, mixed $handler): void;
|
|
||||||
public function getData(): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Route {
|
|
||||||
public function __construct(string $httpMethod, mixed $handler, string $regex, array $variables);
|
|
||||||
public function matches(string $str): bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DataGenerator {
|
|
||||||
public function addRoute(string $httpMethod, array $routeData, mixed $handler);
|
|
||||||
public function getData(): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dispatcher {
|
|
||||||
const int NOT_FOUND = 0;
|
|
||||||
const int FOUND = 1;
|
|
||||||
const int METHOD_NOT_ALLOWED = 2;
|
|
||||||
public function dispatch(string $httpMethod, string $uri): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
function simpleDispatcher(
|
|
||||||
(function(RouteCollector): void) $routeDefinitionCallback,
|
|
||||||
shape(
|
|
||||||
?'routeParser' => classname<RouteParser>,
|
|
||||||
?'dataGenerator' => classname<DataGenerator>,
|
|
||||||
?'dispatcher' => classname<Dispatcher>,
|
|
||||||
?'routeCollector' => classname<RouteCollector>,
|
|
||||||
) $options = shape()): Dispatcher;
|
|
||||||
|
|
||||||
function cachedDispatcher(
|
|
||||||
(function(RouteCollector): void) $routeDefinitionCallback,
|
|
||||||
shape(
|
|
||||||
?'routeParser' => classname<RouteParser>,
|
|
||||||
?'dataGenerator' => classname<DataGenerator>,
|
|
||||||
?'dispatcher' => classname<Dispatcher>,
|
|
||||||
?'routeCollector' => classname<RouteCollector>,
|
|
||||||
?'cacheDisabled' => bool,
|
|
||||||
?'cacheFile' => string,
|
|
||||||
) $options = shape()): Dispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace FastRoute\DataGenerator {
|
|
||||||
abstract class RegexBasedAbstract implements \FastRoute\DataGenerator {
|
|
||||||
protected abstract function getApproxChunkSize();
|
|
||||||
protected abstract function processChunk($regexToRoutesMap);
|
|
||||||
|
|
||||||
public function addRoute(string $httpMethod, array $routeData, mixed $handler): void;
|
|
||||||
public function getData(): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CharCountBased extends RegexBasedAbstract {
|
|
||||||
protected function getApproxChunkSize(): int;
|
|
||||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupCountBased extends RegexBasedAbstract {
|
|
||||||
protected function getApproxChunkSize(): int;
|
|
||||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupPosBased extends RegexBasedAbstract {
|
|
||||||
protected function getApproxChunkSize(): int;
|
|
||||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MarkBased extends RegexBasedAbstract {
|
|
||||||
protected function getApproxChunkSize(): int;
|
|
||||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher {
|
|
||||||
abstract class RegexBasedAbstract implements \FastRoute\Dispatcher {
|
|
||||||
protected abstract function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
|
||||||
|
|
||||||
public function dispatch(string $httpMethod, string $uri): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupPosBased extends RegexBasedAbstract {
|
|
||||||
public function __construct(array $data);
|
|
||||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupCountBased extends RegexBasedAbstract {
|
|
||||||
public function __construct(array $data);
|
|
||||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CharCountBased extends RegexBasedAbstract {
|
|
||||||
public function __construct(array $data);
|
|
||||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MarkBased extends RegexBasedAbstract {
|
|
||||||
public function __construct(array $data);
|
|
||||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace FastRoute\RouteParser {
|
|
||||||
class Std implements \FastRoute\RouteParser {
|
|
||||||
const string VARIABLE_REGEX = <<<'REGEX'
|
|
||||||
\{
|
|
||||||
\s* ([a-zA-Z][a-zA-Z0-9_]*) \s*
|
|
||||||
(?:
|
|
||||||
: \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*)
|
|
||||||
)?
|
|
||||||
\}
|
|
||||||
REGEX;
|
|
||||||
const string DEFAULT_DISPATCH_REGEX = '[^/]+';
|
|
||||||
public function parse(string $route): array<array>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
vendor/nikic/fast-route/LICENSE
vendored
31
vendor/nikic/fast-route/LICENSE
vendored
@@ -1,31 +0,0 @@
|
|||||||
Copyright (c) 2013 by Nikita Popov.
|
|
||||||
|
|
||||||
Some rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following
|
|
||||||
disclaimer in the documentation and/or other materials provided
|
|
||||||
with the distribution.
|
|
||||||
|
|
||||||
* The names of the contributors may not be used to endorse or
|
|
||||||
promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
313
vendor/nikic/fast-route/README.md
vendored
313
vendor/nikic/fast-route/README.md
vendored
@@ -1,313 +0,0 @@
|
|||||||
FastRoute - Fast request router for PHP
|
|
||||||
=======================================
|
|
||||||
|
|
||||||
This library provides a fast implementation of a regular expression based router. [Blog post explaining how the
|
|
||||||
implementation works and why it is fast.][blog_post]
|
|
||||||
|
|
||||||
Install
|
|
||||||
-------
|
|
||||||
|
|
||||||
To install with composer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
composer require nikic/fast-route
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires PHP 5.4 or newer.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Here's a basic usage example:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
require '/path/to/vendor/autoload.php';
|
|
||||||
|
|
||||||
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/users', 'get_all_users_handler');
|
|
||||||
// {id} must be a number (\d+)
|
|
||||||
$r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler');
|
|
||||||
// The /{title} suffix is optional
|
|
||||||
$r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch method and URI from somewhere
|
|
||||||
$httpMethod = $_SERVER['REQUEST_METHOD'];
|
|
||||||
$uri = $_SERVER['REQUEST_URI'];
|
|
||||||
|
|
||||||
// Strip query string (?foo=bar) and decode URI
|
|
||||||
if (false !== $pos = strpos($uri, '?')) {
|
|
||||||
$uri = substr($uri, 0, $pos);
|
|
||||||
}
|
|
||||||
$uri = rawurldecode($uri);
|
|
||||||
|
|
||||||
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
|
|
||||||
switch ($routeInfo[0]) {
|
|
||||||
case FastRoute\Dispatcher::NOT_FOUND:
|
|
||||||
// ... 404 Not Found
|
|
||||||
break;
|
|
||||||
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
|
|
||||||
$allowedMethods = $routeInfo[1];
|
|
||||||
// ... 405 Method Not Allowed
|
|
||||||
break;
|
|
||||||
case FastRoute\Dispatcher::FOUND:
|
|
||||||
$handler = $routeInfo[1];
|
|
||||||
$vars = $routeInfo[2];
|
|
||||||
// ... call $handler with $vars
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Defining routes
|
|
||||||
|
|
||||||
The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts
|
|
||||||
a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling
|
|
||||||
`addRoute()` on the collector instance:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$r->addRoute($method, $routePattern, $handler);
|
|
||||||
```
|
|
||||||
|
|
||||||
The `$method` is an uppercase HTTP method string for which a certain route should match. It
|
|
||||||
is possible to specify multiple valid methods using an array:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// These two calls
|
|
||||||
$r->addRoute('GET', '/test', 'handler');
|
|
||||||
$r->addRoute('POST', '/test', 'handler');
|
|
||||||
// Are equivalent to this one call
|
|
||||||
$r->addRoute(['GET', 'POST'], '/test', 'handler');
|
|
||||||
```
|
|
||||||
|
|
||||||
By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo`
|
|
||||||
and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify
|
|
||||||
a custom pattern by writing `{bar:[0-9]+}`. Some examples:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Matches /user/42, but not /user/xyz
|
|
||||||
$r->addRoute('GET', '/user/{id:\d+}', 'handler');
|
|
||||||
|
|
||||||
// Matches /user/foobar, but not /user/foo/bar
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler');
|
|
||||||
|
|
||||||
// Matches /user/foo/bar as well
|
|
||||||
$r->addRoute('GET', '/user/{name:.+}', 'handler');
|
|
||||||
```
|
|
||||||
|
|
||||||
Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}`
|
|
||||||
is not a valid placeholder, because `()` is a capturing group. Instead you can use either
|
|
||||||
`{lang:en|de}` or `{lang:(?:en|de)}`.
|
|
||||||
|
|
||||||
Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]`
|
|
||||||
will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position,
|
|
||||||
not in the middle of a route.
|
|
||||||
|
|
||||||
```php
|
|
||||||
// This route
|
|
||||||
$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler');
|
|
||||||
// Is equivalent to these two routes
|
|
||||||
$r->addRoute('GET', '/user/{id:\d+}', 'handler');
|
|
||||||
$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler');
|
|
||||||
|
|
||||||
// Multiple nested optional parts are possible as well
|
|
||||||
$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler');
|
|
||||||
|
|
||||||
// This route is NOT valid, because optional parts can only occur at the end
|
|
||||||
$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler');
|
|
||||||
```
|
|
||||||
|
|
||||||
The `$handler` parameter does not necessarily have to be a callback, it could also be a controller
|
|
||||||
class name or any other kind of data you wish to associate with the route. FastRoute only tells you
|
|
||||||
which handler corresponds to your URI, how you interpret it is up to you.
|
|
||||||
|
|
||||||
#### Shorcut methods for common request methods
|
|
||||||
|
|
||||||
For the `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` request methods shortcut methods are available. For example:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$r->get('/get-route', 'get_handler');
|
|
||||||
$r->post('/post-route', 'post_handler');
|
|
||||||
```
|
|
||||||
|
|
||||||
Is equivalent to:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$r->addRoute('GET', '/get-route', 'get_handler');
|
|
||||||
$r->addRoute('POST', '/post-route', 'post_handler');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Route Groups
|
|
||||||
|
|
||||||
Additionally, you can specify routes inside of a group. All routes defined inside a group will have a common prefix.
|
|
||||||
|
|
||||||
For example, defining your routes as:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$r->addGroup('/admin', function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/do-something', 'handler');
|
|
||||||
$r->addRoute('GET', '/do-another-thing', 'handler');
|
|
||||||
$r->addRoute('GET', '/do-something-else', 'handler');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Will have the same result as:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$r->addRoute('GET', '/admin/do-something', 'handler');
|
|
||||||
$r->addRoute('GET', '/admin/do-another-thing', 'handler');
|
|
||||||
$r->addRoute('GET', '/admin/do-something-else', 'handler');
|
|
||||||
```
|
|
||||||
|
|
||||||
Nested groups are also supported, in which case the prefixes of all the nested groups are combined.
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
|
|
||||||
The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless
|
|
||||||
caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated
|
|
||||||
routing data and construct the dispatcher from the cached information:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
$dispatcher = FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler2');
|
|
||||||
}, [
|
|
||||||
'cacheFile' => __DIR__ . '/route.cache', /* required */
|
|
||||||
'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
The second parameter to the function is an options array, which can be used to specify the cache
|
|
||||||
file location, among other things.
|
|
||||||
|
|
||||||
### Dispatching a URI
|
|
||||||
|
|
||||||
A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method
|
|
||||||
accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them
|
|
||||||
appropriately) is your job - this library is not bound to the PHP web SAPIs.
|
|
||||||
|
|
||||||
The `dispatch()` method returns an array whose first element contains a status code. It is one
|
|
||||||
of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the
|
|
||||||
method not allowed status the second array element contains a list of HTTP methods allowed for
|
|
||||||
the supplied URI. For example:
|
|
||||||
|
|
||||||
[FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']]
|
|
||||||
|
|
||||||
> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the
|
|
||||||
`Allow:` header to detail available methods for the requested resource. Applications using FastRoute
|
|
||||||
should use the second array element to add this header when relaying a 405 response.
|
|
||||||
|
|
||||||
For the found status the second array element is the handler that was associated with the route
|
|
||||||
and the third array element is a dictionary of placeholder names to their values. For example:
|
|
||||||
|
|
||||||
/* Routing against GET /user/nikic/42 */
|
|
||||||
|
|
||||||
[FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']]
|
|
||||||
|
|
||||||
### Overriding the route parser and dispatcher
|
|
||||||
|
|
||||||
The routing process makes use of three components: A route parser, a data generator and a
|
|
||||||
dispatcher. The three components adhere to the following interfaces:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
interface RouteParser {
|
|
||||||
public function parse($route);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DataGenerator {
|
|
||||||
public function addRoute($httpMethod, $routeData, $handler);
|
|
||||||
public function getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dispatcher {
|
|
||||||
const NOT_FOUND = 0, FOUND = 1, METHOD_NOT_ALLOWED = 2;
|
|
||||||
|
|
||||||
public function dispatch($httpMethod, $uri);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The route parser takes a route pattern string and converts it into an array of route infos, where
|
|
||||||
each route info is again an array of it's parts. The structure is best understood using an example:
|
|
||||||
|
|
||||||
/* The route /user/{id:\d+}[/{name}] converts to the following array: */
|
|
||||||
[
|
|
||||||
[
|
|
||||||
'/user/',
|
|
||||||
['id', '\d+'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/user/',
|
|
||||||
['id', '\d+'],
|
|
||||||
'/',
|
|
||||||
['name', '[^/]+'],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
This array can then be passed to the `addRoute()` method of a data generator. After all routes have
|
|
||||||
been added the `getData()` of the generator is invoked, which returns all the routing data required
|
|
||||||
by the dispatcher. The format of this data is not further specified - it is tightly coupled to
|
|
||||||
the corresponding dispatcher.
|
|
||||||
|
|
||||||
The dispatcher accepts the routing data via a constructor and provides a `dispatch()` method, which
|
|
||||||
you're already familiar with.
|
|
||||||
|
|
||||||
The route parser can be overwritten individually (to make use of some different pattern syntax),
|
|
||||||
however the data generator and dispatcher should always be changed as a pair, as the output from
|
|
||||||
the former is tightly coupled to the input of the latter. The reason the generator and the
|
|
||||||
dispatcher are separate is that only the latter is needed when using caching (as the output of
|
|
||||||
the former is what is being cached.)
|
|
||||||
|
|
||||||
When using the `simpleDispatcher` / `cachedDispatcher` functions from above the override happens
|
|
||||||
through the options array:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
|
|
||||||
/* ... */
|
|
||||||
}, [
|
|
||||||
'routeParser' => 'FastRoute\\RouteParser\\Std',
|
|
||||||
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
|
|
||||||
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
The above options array corresponds to the defaults. By replacing `GroupCountBased` by
|
|
||||||
`GroupPosBased` you could switch to a different dispatching strategy.
|
|
||||||
|
|
||||||
### A Note on HEAD Requests
|
|
||||||
|
|
||||||
The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]:
|
|
||||||
|
|
||||||
> The methods GET and HEAD MUST be supported by all general-purpose servers
|
|
||||||
|
|
||||||
To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an
|
|
||||||
available GET route for a given resource. The PHP web SAPI transparently removes the entity body
|
|
||||||
from HEAD responses so this behavior has no effect on the vast majority of users.
|
|
||||||
|
|
||||||
However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST
|
|
||||||
NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is
|
|
||||||
*your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases.
|
|
||||||
|
|
||||||
Finally, note that applications MAY always specify their own HEAD method route for a given
|
|
||||||
resource to bypass this behavior entirely.
|
|
||||||
|
|
||||||
### Credits
|
|
||||||
|
|
||||||
This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server.
|
|
||||||
|
|
||||||
A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey].
|
|
||||||
|
|
||||||
|
|
||||||
[2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1"
|
|
||||||
[blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html
|
|
||||||
[levi]: https://github.com/morrisonlevi
|
|
||||||
[rdlowrey]: https://github.com/rdlowrey
|
|
||||||
24
vendor/nikic/fast-route/composer.json
vendored
24
vendor/nikic/fast-route/composer.json
vendored
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nikic/fast-route",
|
|
||||||
"description": "Fast request router for PHP",
|
|
||||||
"keywords": ["routing", "router"],
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Nikita Popov",
|
|
||||||
"email": "nikic@php.net"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"FastRoute\\": "src/"
|
|
||||||
},
|
|
||||||
"files": ["src/functions.php"]
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.4.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^4.8.35|~5.7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
24
vendor/nikic/fast-route/phpunit.xml
vendored
24
vendor/nikic/fast-route/phpunit.xml
vendored
@@ -1,24 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<phpunit backupGlobals="false"
|
|
||||||
backupStaticAttributes="false"
|
|
||||||
colors="true"
|
|
||||||
convertErrorsToExceptions="true"
|
|
||||||
convertNoticesToExceptions="true"
|
|
||||||
convertWarningsToExceptions="true"
|
|
||||||
processIsolation="false"
|
|
||||||
syntaxCheck="false"
|
|
||||||
bootstrap="test/bootstrap.php"
|
|
||||||
>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="FastRoute Tests">
|
|
||||||
<directory>./test/</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
|
|
||||||
<filter>
|
|
||||||
<whitelist>
|
|
||||||
<directory>./src/</directory>
|
|
||||||
</whitelist>
|
|
||||||
</filter>
|
|
||||||
</phpunit>
|
|
||||||
28
vendor/nikic/fast-route/psalm.xml
vendored
28
vendor/nikic/fast-route/psalm.xml
vendored
@@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<psalm
|
|
||||||
name="Example Psalm config with recommended defaults"
|
|
||||||
stopOnFirstError="false"
|
|
||||||
useDocblockTypes="true"
|
|
||||||
totallyTyped="false"
|
|
||||||
requireVoidReturnType="false"
|
|
||||||
>
|
|
||||||
<projectFiles>
|
|
||||||
<directory name="src" />
|
|
||||||
</projectFiles>
|
|
||||||
|
|
||||||
<issueHandlers>
|
|
||||||
<LessSpecificReturnType errorLevel="info" />
|
|
||||||
|
|
||||||
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
|
|
||||||
<DeprecatedMethod errorLevel="info" />
|
|
||||||
|
|
||||||
<MissingClosureReturnType errorLevel="info" />
|
|
||||||
<MissingReturnType errorLevel="info" />
|
|
||||||
<MissingPropertyType errorLevel="info" />
|
|
||||||
<InvalidDocblock errorLevel="info" />
|
|
||||||
<MisplacedRequiredParam errorLevel="info" />
|
|
||||||
|
|
||||||
<PropertyNotSetInConstructor errorLevel="info" />
|
|
||||||
<MissingConstructor errorLevel="info" />
|
|
||||||
</issueHandlers>
|
|
||||||
</psalm>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
class BadRouteException extends \LogicException
|
|
||||||
{
|
|
||||||
}
|
|
||||||
26
vendor/nikic/fast-route/src/DataGenerator.php
vendored
26
vendor/nikic/fast-route/src/DataGenerator.php
vendored
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
interface DataGenerator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Adds a route to the data generator. The route data uses the
|
|
||||||
* same format that is returned by RouterParser::parser().
|
|
||||||
*
|
|
||||||
* The handler doesn't necessarily need to be a callable, it
|
|
||||||
* can be arbitrary data that will be returned when the route
|
|
||||||
* matches.
|
|
||||||
*
|
|
||||||
* @param string $httpMethod
|
|
||||||
* @param array $routeData
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function addRoute($httpMethod, $routeData, $handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns dispatcher data in some unspecified format, which
|
|
||||||
* depends on the used method of dispatch.
|
|
||||||
*/
|
|
||||||
public function getData();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\DataGenerator;
|
|
||||||
|
|
||||||
class CharCountBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
protected function getApproxChunkSize()
|
|
||||||
{
|
|
||||||
return 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function processChunk($regexToRoutesMap)
|
|
||||||
{
|
|
||||||
$routeMap = [];
|
|
||||||
$regexes = [];
|
|
||||||
|
|
||||||
$suffixLen = 0;
|
|
||||||
$suffix = '';
|
|
||||||
$count = count($regexToRoutesMap);
|
|
||||||
foreach ($regexToRoutesMap as $regex => $route) {
|
|
||||||
$suffixLen++;
|
|
||||||
$suffix .= "\t";
|
|
||||||
|
|
||||||
$regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})';
|
|
||||||
$routeMap[$suffix] = [$route->handler, $route->variables];
|
|
||||||
}
|
|
||||||
|
|
||||||
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
|
|
||||||
return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\DataGenerator;
|
|
||||||
|
|
||||||
class GroupCountBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
protected function getApproxChunkSize()
|
|
||||||
{
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function processChunk($regexToRoutesMap)
|
|
||||||
{
|
|
||||||
$routeMap = [];
|
|
||||||
$regexes = [];
|
|
||||||
$numGroups = 0;
|
|
||||||
foreach ($regexToRoutesMap as $regex => $route) {
|
|
||||||
$numVariables = count($route->variables);
|
|
||||||
$numGroups = max($numGroups, $numVariables);
|
|
||||||
|
|
||||||
$regexes[] = $regex . str_repeat('()', $numGroups - $numVariables);
|
|
||||||
$routeMap[$numGroups + 1] = [$route->handler, $route->variables];
|
|
||||||
|
|
||||||
++$numGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
|
|
||||||
return ['regex' => $regex, 'routeMap' => $routeMap];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\DataGenerator;
|
|
||||||
|
|
||||||
class GroupPosBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
protected function getApproxChunkSize()
|
|
||||||
{
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function processChunk($regexToRoutesMap)
|
|
||||||
{
|
|
||||||
$routeMap = [];
|
|
||||||
$regexes = [];
|
|
||||||
$offset = 1;
|
|
||||||
foreach ($regexToRoutesMap as $regex => $route) {
|
|
||||||
$regexes[] = $regex;
|
|
||||||
$routeMap[$offset] = [$route->handler, $route->variables];
|
|
||||||
|
|
||||||
$offset += count($route->variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
$regex = '~^(?:' . implode('|', $regexes) . ')$~';
|
|
||||||
return ['regex' => $regex, 'routeMap' => $routeMap];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\DataGenerator;
|
|
||||||
|
|
||||||
class MarkBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
protected function getApproxChunkSize()
|
|
||||||
{
|
|
||||||
return 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function processChunk($regexToRoutesMap)
|
|
||||||
{
|
|
||||||
$routeMap = [];
|
|
||||||
$regexes = [];
|
|
||||||
$markName = 'a';
|
|
||||||
foreach ($regexToRoutesMap as $regex => $route) {
|
|
||||||
$regexes[] = $regex . '(*MARK:' . $markName . ')';
|
|
||||||
$routeMap[$markName] = [$route->handler, $route->variables];
|
|
||||||
|
|
||||||
++$markName;
|
|
||||||
}
|
|
||||||
|
|
||||||
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
|
|
||||||
return ['regex' => $regex, 'routeMap' => $routeMap];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\DataGenerator;
|
|
||||||
|
|
||||||
use FastRoute\BadRouteException;
|
|
||||||
use FastRoute\DataGenerator;
|
|
||||||
use FastRoute\Route;
|
|
||||||
|
|
||||||
abstract class RegexBasedAbstract implements DataGenerator
|
|
||||||
{
|
|
||||||
/** @var mixed[][] */
|
|
||||||
protected $staticRoutes = [];
|
|
||||||
|
|
||||||
/** @var Route[][] */
|
|
||||||
protected $methodToRegexToRoutesMap = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
abstract protected function getApproxChunkSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
abstract protected function processChunk($regexToRoutesMap);
|
|
||||||
|
|
||||||
public function addRoute($httpMethod, $routeData, $handler)
|
|
||||||
{
|
|
||||||
if ($this->isStaticRoute($routeData)) {
|
|
||||||
$this->addStaticRoute($httpMethod, $routeData, $handler);
|
|
||||||
} else {
|
|
||||||
$this->addVariableRoute($httpMethod, $routeData, $handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
public function getData()
|
|
||||||
{
|
|
||||||
if (empty($this->methodToRegexToRoutesMap)) {
|
|
||||||
return [$this->staticRoutes, []];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$this->staticRoutes, $this->generateVariableRouteData()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
private function generateVariableRouteData()
|
|
||||||
{
|
|
||||||
$data = [];
|
|
||||||
foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) {
|
|
||||||
$chunkSize = $this->computeChunkSize(count($regexToRoutesMap));
|
|
||||||
$chunks = array_chunk($regexToRoutesMap, $chunkSize, true);
|
|
||||||
$data[$method] = array_map([$this, 'processChunk'], $chunks);
|
|
||||||
}
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
private function computeChunkSize($count)
|
|
||||||
{
|
|
||||||
$numParts = max(1, round($count / $this->getApproxChunkSize()));
|
|
||||||
return (int) ceil($count / $numParts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed[]
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function isStaticRoute($routeData)
|
|
||||||
{
|
|
||||||
return count($routeData) === 1 && is_string($routeData[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addStaticRoute($httpMethod, $routeData, $handler)
|
|
||||||
{
|
|
||||||
$routeStr = $routeData[0];
|
|
||||||
|
|
||||||
if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
|
|
||||||
throw new BadRouteException(sprintf(
|
|
||||||
'Cannot register two routes matching "%s" for method "%s"',
|
|
||||||
$routeStr, $httpMethod
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {
|
|
||||||
foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {
|
|
||||||
if ($route->matches($routeStr)) {
|
|
||||||
throw new BadRouteException(sprintf(
|
|
||||||
'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"',
|
|
||||||
$routeStr, $route->regex, $httpMethod
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->staticRoutes[$httpMethod][$routeStr] = $handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addVariableRoute($httpMethod, $routeData, $handler)
|
|
||||||
{
|
|
||||||
list($regex, $variables) = $this->buildRegexForRoute($routeData);
|
|
||||||
|
|
||||||
if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {
|
|
||||||
throw new BadRouteException(sprintf(
|
|
||||||
'Cannot register two routes matching "%s" for method "%s"',
|
|
||||||
$regex, $httpMethod
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route(
|
|
||||||
$httpMethod, $handler, $regex, $variables
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed[]
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
private function buildRegexForRoute($routeData)
|
|
||||||
{
|
|
||||||
$regex = '';
|
|
||||||
$variables = [];
|
|
||||||
foreach ($routeData as $part) {
|
|
||||||
if (is_string($part)) {
|
|
||||||
$regex .= preg_quote($part, '~');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
list($varName, $regexPart) = $part;
|
|
||||||
|
|
||||||
if (isset($variables[$varName])) {
|
|
||||||
throw new BadRouteException(sprintf(
|
|
||||||
'Cannot use the same placeholder "%s" twice', $varName
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->regexHasCapturingGroups($regexPart)) {
|
|
||||||
throw new BadRouteException(sprintf(
|
|
||||||
'Regex "%s" for parameter "%s" contains a capturing group',
|
|
||||||
$regexPart, $varName
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$variables[$varName] = $varName;
|
|
||||||
$regex .= '(' . $regexPart . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$regex, $variables];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function regexHasCapturingGroups($regex)
|
|
||||||
{
|
|
||||||
if (false === strpos($regex, '(')) {
|
|
||||||
// Needs to have at least a ( to contain a capturing group
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semi-accurate detection for capturing groups
|
|
||||||
return (bool) preg_match(
|
|
||||||
'~
|
|
||||||
(?:
|
|
||||||
\(\?\(
|
|
||||||
| \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \]
|
|
||||||
| \\\\ .
|
|
||||||
) (*SKIP)(*FAIL) |
|
|
||||||
\(
|
|
||||||
(?!
|
|
||||||
\? (?! <(?![!=]) | P< | \' )
|
|
||||||
| \*
|
|
||||||
)
|
|
||||||
~x',
|
|
||||||
$regex
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
vendor/nikic/fast-route/src/Dispatcher.php
vendored
26
vendor/nikic/fast-route/src/Dispatcher.php
vendored
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
interface Dispatcher
|
|
||||||
{
|
|
||||||
const NOT_FOUND = 0;
|
|
||||||
const FOUND = 1;
|
|
||||||
const METHOD_NOT_ALLOWED = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches against the provided HTTP method verb and URI.
|
|
||||||
*
|
|
||||||
* Returns array with one of the following formats:
|
|
||||||
*
|
|
||||||
* [self::NOT_FOUND]
|
|
||||||
* [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
|
|
||||||
* [self::FOUND, $handler, ['varName' => 'value', ...]]
|
|
||||||
*
|
|
||||||
* @param string $httpMethod
|
|
||||||
* @param string $uri
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function dispatch($httpMethod, $uri);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class CharCountBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
public function __construct($data)
|
|
||||||
{
|
|
||||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function dispatchVariableRoute($routeData, $uri)
|
|
||||||
{
|
|
||||||
foreach ($routeData as $data) {
|
|
||||||
if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
list($handler, $varNames) = $data['routeMap'][end($matches)];
|
|
||||||
|
|
||||||
$vars = [];
|
|
||||||
$i = 0;
|
|
||||||
foreach ($varNames as $varName) {
|
|
||||||
$vars[$varName] = $matches[++$i];
|
|
||||||
}
|
|
||||||
return [self::FOUND, $handler, $vars];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [self::NOT_FOUND];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class GroupCountBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
public function __construct($data)
|
|
||||||
{
|
|
||||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function dispatchVariableRoute($routeData, $uri)
|
|
||||||
{
|
|
||||||
foreach ($routeData as $data) {
|
|
||||||
if (!preg_match($data['regex'], $uri, $matches)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
list($handler, $varNames) = $data['routeMap'][count($matches)];
|
|
||||||
|
|
||||||
$vars = [];
|
|
||||||
$i = 0;
|
|
||||||
foreach ($varNames as $varName) {
|
|
||||||
$vars[$varName] = $matches[++$i];
|
|
||||||
}
|
|
||||||
return [self::FOUND, $handler, $vars];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [self::NOT_FOUND];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class GroupPosBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
public function __construct($data)
|
|
||||||
{
|
|
||||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function dispatchVariableRoute($routeData, $uri)
|
|
||||||
{
|
|
||||||
foreach ($routeData as $data) {
|
|
||||||
if (!preg_match($data['regex'], $uri, $matches)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find first non-empty match
|
|
||||||
for ($i = 1; '' === $matches[$i]; ++$i);
|
|
||||||
|
|
||||||
list($handler, $varNames) = $data['routeMap'][$i];
|
|
||||||
|
|
||||||
$vars = [];
|
|
||||||
foreach ($varNames as $varName) {
|
|
||||||
$vars[$varName] = $matches[$i++];
|
|
||||||
}
|
|
||||||
return [self::FOUND, $handler, $vars];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [self::NOT_FOUND];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class MarkBased extends RegexBasedAbstract
|
|
||||||
{
|
|
||||||
public function __construct($data)
|
|
||||||
{
|
|
||||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function dispatchVariableRoute($routeData, $uri)
|
|
||||||
{
|
|
||||||
foreach ($routeData as $data) {
|
|
||||||
if (!preg_match($data['regex'], $uri, $matches)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
list($handler, $varNames) = $data['routeMap'][$matches['MARK']];
|
|
||||||
|
|
||||||
$vars = [];
|
|
||||||
$i = 0;
|
|
||||||
foreach ($varNames as $varName) {
|
|
||||||
$vars[$varName] = $matches[++$i];
|
|
||||||
}
|
|
||||||
return [self::FOUND, $handler, $vars];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [self::NOT_FOUND];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
use FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
abstract class RegexBasedAbstract implements Dispatcher
|
|
||||||
{
|
|
||||||
/** @var mixed[][] */
|
|
||||||
protected $staticRouteMap = [];
|
|
||||||
|
|
||||||
/** @var mixed[] */
|
|
||||||
protected $variableRouteData = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
abstract protected function dispatchVariableRoute($routeData, $uri);
|
|
||||||
|
|
||||||
public function dispatch($httpMethod, $uri)
|
|
||||||
{
|
|
||||||
if (isset($this->staticRouteMap[$httpMethod][$uri])) {
|
|
||||||
$handler = $this->staticRouteMap[$httpMethod][$uri];
|
|
||||||
return [self::FOUND, $handler, []];
|
|
||||||
}
|
|
||||||
|
|
||||||
$varRouteData = $this->variableRouteData;
|
|
||||||
if (isset($varRouteData[$httpMethod])) {
|
|
||||||
$result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
|
|
||||||
if ($result[0] === self::FOUND) {
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For HEAD requests, attempt fallback to GET
|
|
||||||
if ($httpMethod === 'HEAD') {
|
|
||||||
if (isset($this->staticRouteMap['GET'][$uri])) {
|
|
||||||
$handler = $this->staticRouteMap['GET'][$uri];
|
|
||||||
return [self::FOUND, $handler, []];
|
|
||||||
}
|
|
||||||
if (isset($varRouteData['GET'])) {
|
|
||||||
$result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
|
|
||||||
if ($result[0] === self::FOUND) {
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If nothing else matches, try fallback routes
|
|
||||||
if (isset($this->staticRouteMap['*'][$uri])) {
|
|
||||||
$handler = $this->staticRouteMap['*'][$uri];
|
|
||||||
return [self::FOUND, $handler, []];
|
|
||||||
}
|
|
||||||
if (isset($varRouteData['*'])) {
|
|
||||||
$result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
|
|
||||||
if ($result[0] === self::FOUND) {
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find allowed methods for this URI by matching against all other HTTP methods as well
|
|
||||||
$allowedMethods = [];
|
|
||||||
|
|
||||||
foreach ($this->staticRouteMap as $method => $uriMap) {
|
|
||||||
if ($method !== $httpMethod && isset($uriMap[$uri])) {
|
|
||||||
$allowedMethods[] = $method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($varRouteData as $method => $routeData) {
|
|
||||||
if ($method === $httpMethod) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $this->dispatchVariableRoute($routeData, $uri);
|
|
||||||
if ($result[0] === self::FOUND) {
|
|
||||||
$allowedMethods[] = $method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no allowed methods the route simply does not exist
|
|
||||||
if ($allowedMethods) {
|
|
||||||
return [self::METHOD_NOT_ALLOWED, $allowedMethods];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [self::NOT_FOUND];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
vendor/nikic/fast-route/src/Route.php
vendored
47
vendor/nikic/fast-route/src/Route.php
vendored
@@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
class Route
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
public $httpMethod;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $regex;
|
|
||||||
|
|
||||||
/** @var array */
|
|
||||||
public $variables;
|
|
||||||
|
|
||||||
/** @var mixed */
|
|
||||||
public $handler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a route (value object).
|
|
||||||
*
|
|
||||||
* @param string $httpMethod
|
|
||||||
* @param mixed $handler
|
|
||||||
* @param string $regex
|
|
||||||
* @param array $variables
|
|
||||||
*/
|
|
||||||
public function __construct($httpMethod, $handler, $regex, $variables)
|
|
||||||
{
|
|
||||||
$this->httpMethod = $httpMethod;
|
|
||||||
$this->handler = $handler;
|
|
||||||
$this->regex = $regex;
|
|
||||||
$this->variables = $variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests whether this route matches the given string.
|
|
||||||
*
|
|
||||||
* @param string $str
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function matches($str)
|
|
||||||
{
|
|
||||||
$regex = '~^' . $this->regex . '$~';
|
|
||||||
return (bool) preg_match($regex, $str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
152
vendor/nikic/fast-route/src/RouteCollector.php
vendored
152
vendor/nikic/fast-route/src/RouteCollector.php
vendored
@@ -1,152 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
class RouteCollector
|
|
||||||
{
|
|
||||||
/** @var RouteParser */
|
|
||||||
protected $routeParser;
|
|
||||||
|
|
||||||
/** @var DataGenerator */
|
|
||||||
protected $dataGenerator;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $currentGroupPrefix;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a route collector.
|
|
||||||
*
|
|
||||||
* @param RouteParser $routeParser
|
|
||||||
* @param DataGenerator $dataGenerator
|
|
||||||
*/
|
|
||||||
public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator)
|
|
||||||
{
|
|
||||||
$this->routeParser = $routeParser;
|
|
||||||
$this->dataGenerator = $dataGenerator;
|
|
||||||
$this->currentGroupPrefix = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a route to the collection.
|
|
||||||
*
|
|
||||||
* The syntax used in the $route string depends on the used route parser.
|
|
||||||
*
|
|
||||||
* @param string|string[] $httpMethod
|
|
||||||
* @param string $route
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function addRoute($httpMethod, $route, $handler)
|
|
||||||
{
|
|
||||||
$route = $this->currentGroupPrefix . $route;
|
|
||||||
$routeDatas = $this->routeParser->parse($route);
|
|
||||||
foreach ((array) $httpMethod as $method) {
|
|
||||||
foreach ($routeDatas as $routeData) {
|
|
||||||
$this->dataGenerator->addRoute($method, $routeData, $handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a route group with a common prefix.
|
|
||||||
*
|
|
||||||
* All routes created in the passed callback will have the given group prefix prepended.
|
|
||||||
*
|
|
||||||
* @param string $prefix
|
|
||||||
* @param callable $callback
|
|
||||||
*/
|
|
||||||
public function addGroup($prefix, callable $callback)
|
|
||||||
{
|
|
||||||
$previousGroupPrefix = $this->currentGroupPrefix;
|
|
||||||
$this->currentGroupPrefix = $previousGroupPrefix . $prefix;
|
|
||||||
$callback($this);
|
|
||||||
$this->currentGroupPrefix = $previousGroupPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a GET route to the collection
|
|
||||||
*
|
|
||||||
* This is simply an alias of $this->addRoute('GET', $route, $handler)
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function get($route, $handler)
|
|
||||||
{
|
|
||||||
$this->addRoute('GET', $route, $handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a POST route to the collection
|
|
||||||
*
|
|
||||||
* This is simply an alias of $this->addRoute('POST', $route, $handler)
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function post($route, $handler)
|
|
||||||
{
|
|
||||||
$this->addRoute('POST', $route, $handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a PUT route to the collection
|
|
||||||
*
|
|
||||||
* This is simply an alias of $this->addRoute('PUT', $route, $handler)
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function put($route, $handler)
|
|
||||||
{
|
|
||||||
$this->addRoute('PUT', $route, $handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a DELETE route to the collection
|
|
||||||
*
|
|
||||||
* This is simply an alias of $this->addRoute('DELETE', $route, $handler)
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function delete($route, $handler)
|
|
||||||
{
|
|
||||||
$this->addRoute('DELETE', $route, $handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a PATCH route to the collection
|
|
||||||
*
|
|
||||||
* This is simply an alias of $this->addRoute('PATCH', $route, $handler)
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function patch($route, $handler)
|
|
||||||
{
|
|
||||||
$this->addRoute('PATCH', $route, $handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a HEAD route to the collection
|
|
||||||
*
|
|
||||||
* This is simply an alias of $this->addRoute('HEAD', $route, $handler)
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param mixed $handler
|
|
||||||
*/
|
|
||||||
public function head($route, $handler)
|
|
||||||
{
|
|
||||||
$this->addRoute('HEAD', $route, $handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the collected route data, as provided by the data generator.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getData()
|
|
||||||
{
|
|
||||||
return $this->dataGenerator->getData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
37
vendor/nikic/fast-route/src/RouteParser.php
vendored
37
vendor/nikic/fast-route/src/RouteParser.php
vendored
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
interface RouteParser
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Parses a route string into multiple route data arrays.
|
|
||||||
*
|
|
||||||
* The expected output is defined using an example:
|
|
||||||
*
|
|
||||||
* For the route string "/fixedRoutePart/{varName}[/moreFixed/{varName2:\d+}]", if {varName} is interpreted as
|
|
||||||
* a placeholder and [...] is interpreted as an optional route part, the expected result is:
|
|
||||||
*
|
|
||||||
* [
|
|
||||||
* // first route: without optional part
|
|
||||||
* [
|
|
||||||
* "/fixedRoutePart/",
|
|
||||||
* ["varName", "[^/]+"],
|
|
||||||
* ],
|
|
||||||
* // second route: with optional part
|
|
||||||
* [
|
|
||||||
* "/fixedRoutePart/",
|
|
||||||
* ["varName", "[^/]+"],
|
|
||||||
* "/moreFixed/",
|
|
||||||
* ["varName2", [0-9]+"],
|
|
||||||
* ],
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* Here one route string was converted into two route data arrays.
|
|
||||||
*
|
|
||||||
* @param string $route Route string to parse
|
|
||||||
*
|
|
||||||
* @return mixed[][] Array of route data arrays
|
|
||||||
*/
|
|
||||||
public function parse($route);
|
|
||||||
}
|
|
||||||
87
vendor/nikic/fast-route/src/RouteParser/Std.php
vendored
87
vendor/nikic/fast-route/src/RouteParser/Std.php
vendored
@@ -1,87 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\RouteParser;
|
|
||||||
|
|
||||||
use FastRoute\BadRouteException;
|
|
||||||
use FastRoute\RouteParser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses route strings of the following form:
|
|
||||||
*
|
|
||||||
* "/user/{name}[/{id:[0-9]+}]"
|
|
||||||
*/
|
|
||||||
class Std implements RouteParser
|
|
||||||
{
|
|
||||||
const VARIABLE_REGEX = <<<'REGEX'
|
|
||||||
\{
|
|
||||||
\s* ([a-zA-Z_][a-zA-Z0-9_-]*) \s*
|
|
||||||
(?:
|
|
||||||
: \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*)
|
|
||||||
)?
|
|
||||||
\}
|
|
||||||
REGEX;
|
|
||||||
const DEFAULT_DISPATCH_REGEX = '[^/]+';
|
|
||||||
|
|
||||||
public function parse($route)
|
|
||||||
{
|
|
||||||
$routeWithoutClosingOptionals = rtrim($route, ']');
|
|
||||||
$numOptionals = strlen($route) - strlen($routeWithoutClosingOptionals);
|
|
||||||
|
|
||||||
// Split on [ while skipping placeholders
|
|
||||||
$segments = preg_split('~' . self::VARIABLE_REGEX . '(*SKIP)(*F) | \[~x', $routeWithoutClosingOptionals);
|
|
||||||
if ($numOptionals !== count($segments) - 1) {
|
|
||||||
// If there are any ] in the middle of the route, throw a more specific error message
|
|
||||||
if (preg_match('~' . self::VARIABLE_REGEX . '(*SKIP)(*F) | \]~x', $routeWithoutClosingOptionals)) {
|
|
||||||
throw new BadRouteException('Optional segments can only occur at the end of a route');
|
|
||||||
}
|
|
||||||
throw new BadRouteException("Number of opening '[' and closing ']' does not match");
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentRoute = '';
|
|
||||||
$routeDatas = [];
|
|
||||||
foreach ($segments as $n => $segment) {
|
|
||||||
if ($segment === '' && $n !== 0) {
|
|
||||||
throw new BadRouteException('Empty optional part');
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentRoute .= $segment;
|
|
||||||
$routeDatas[] = $this->parsePlaceholders($currentRoute);
|
|
||||||
}
|
|
||||||
return $routeDatas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a route string that does not contain optional segments.
|
|
||||||
*
|
|
||||||
* @param string
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
private function parsePlaceholders($route)
|
|
||||||
{
|
|
||||||
if (!preg_match_all(
|
|
||||||
'~' . self::VARIABLE_REGEX . '~x', $route, $matches,
|
|
||||||
PREG_OFFSET_CAPTURE | PREG_SET_ORDER
|
|
||||||
)) {
|
|
||||||
return [$route];
|
|
||||||
}
|
|
||||||
|
|
||||||
$offset = 0;
|
|
||||||
$routeData = [];
|
|
||||||
foreach ($matches as $set) {
|
|
||||||
if ($set[0][1] > $offset) {
|
|
||||||
$routeData[] = substr($route, $offset, $set[0][1] - $offset);
|
|
||||||
}
|
|
||||||
$routeData[] = [
|
|
||||||
$set[1][0],
|
|
||||||
isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX
|
|
||||||
];
|
|
||||||
$offset = $set[0][1] + strlen($set[0][0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($offset !== strlen($route)) {
|
|
||||||
$routeData[] = substr($route, $offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $routeData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
vendor/nikic/fast-route/src/bootstrap.php
vendored
12
vendor/nikic/fast-route/src/bootstrap.php
vendored
@@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
require __DIR__ . '/functions.php';
|
|
||||||
|
|
||||||
spl_autoload_register(function ($class) {
|
|
||||||
if (strpos($class, 'FastRoute\\') === 0) {
|
|
||||||
$name = substr($class, strlen('FastRoute'));
|
|
||||||
require __DIR__ . strtr($name, '\\', DIRECTORY_SEPARATOR) . '.php';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
74
vendor/nikic/fast-route/src/functions.php
vendored
74
vendor/nikic/fast-route/src/functions.php
vendored
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
if (!function_exists('FastRoute\simpleDispatcher')) {
|
|
||||||
/**
|
|
||||||
* @param callable $routeDefinitionCallback
|
|
||||||
* @param array $options
|
|
||||||
*
|
|
||||||
* @return Dispatcher
|
|
||||||
*/
|
|
||||||
function simpleDispatcher(callable $routeDefinitionCallback, array $options = [])
|
|
||||||
{
|
|
||||||
$options += [
|
|
||||||
'routeParser' => 'FastRoute\\RouteParser\\Std',
|
|
||||||
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
|
|
||||||
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
|
|
||||||
'routeCollector' => 'FastRoute\\RouteCollector',
|
|
||||||
];
|
|
||||||
|
|
||||||
/** @var RouteCollector $routeCollector */
|
|
||||||
$routeCollector = new $options['routeCollector'](
|
|
||||||
new $options['routeParser'], new $options['dataGenerator']
|
|
||||||
);
|
|
||||||
$routeDefinitionCallback($routeCollector);
|
|
||||||
|
|
||||||
return new $options['dispatcher']($routeCollector->getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable $routeDefinitionCallback
|
|
||||||
* @param array $options
|
|
||||||
*
|
|
||||||
* @return Dispatcher
|
|
||||||
*/
|
|
||||||
function cachedDispatcher(callable $routeDefinitionCallback, array $options = [])
|
|
||||||
{
|
|
||||||
$options += [
|
|
||||||
'routeParser' => 'FastRoute\\RouteParser\\Std',
|
|
||||||
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
|
|
||||||
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
|
|
||||||
'routeCollector' => 'FastRoute\\RouteCollector',
|
|
||||||
'cacheDisabled' => false,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!isset($options['cacheFile'])) {
|
|
||||||
throw new \LogicException('Must specify "cacheFile" option');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) {
|
|
||||||
$dispatchData = require $options['cacheFile'];
|
|
||||||
if (!is_array($dispatchData)) {
|
|
||||||
throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"');
|
|
||||||
}
|
|
||||||
return new $options['dispatcher']($dispatchData);
|
|
||||||
}
|
|
||||||
|
|
||||||
$routeCollector = new $options['routeCollector'](
|
|
||||||
new $options['routeParser'], new $options['dataGenerator']
|
|
||||||
);
|
|
||||||
$routeDefinitionCallback($routeCollector);
|
|
||||||
|
|
||||||
/** @var RouteCollector $routeCollector */
|
|
||||||
$dispatchData = $routeCollector->getData();
|
|
||||||
if (!$options['cacheDisabled']) {
|
|
||||||
file_put_contents(
|
|
||||||
$options['cacheFile'],
|
|
||||||
'<?php return ' . var_export($dispatchData, true) . ';'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new $options['dispatcher']($dispatchData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class CharCountBasedTest extends DispatcherTest
|
|
||||||
{
|
|
||||||
protected function getDispatcherClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\Dispatcher\\CharCountBased';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDataGeneratorClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\DataGenerator\\CharCountBased';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,581 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
use FastRoute\RouteCollector;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
abstract class DispatcherTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Delegate dispatcher selection to child test classes
|
|
||||||
*/
|
|
||||||
abstract protected function getDispatcherClass();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delegate dataGenerator selection to child test classes
|
|
||||||
*/
|
|
||||||
abstract protected function getDataGeneratorClass();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set appropriate options for the specific Dispatcher class we're testing
|
|
||||||
*/
|
|
||||||
private function generateDispatcherOptions()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'dataGenerator' => $this->getDataGeneratorClass(),
|
|
||||||
'dispatcher' => $this->getDispatcherClass()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider provideFoundDispatchCases
|
|
||||||
*/
|
|
||||||
public function testFoundDispatches($method, $uri, $callback, $handler, $argDict)
|
|
||||||
{
|
|
||||||
$dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
|
|
||||||
$info = $dispatcher->dispatch($method, $uri);
|
|
||||||
$this->assertSame($dispatcher::FOUND, $info[0]);
|
|
||||||
$this->assertSame($handler, $info[1]);
|
|
||||||
$this->assertSame($argDict, $info[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider provideNotFoundDispatchCases
|
|
||||||
*/
|
|
||||||
public function testNotFoundDispatches($method, $uri, $callback)
|
|
||||||
{
|
|
||||||
$dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
|
|
||||||
$routeInfo = $dispatcher->dispatch($method, $uri);
|
|
||||||
$this->assertArrayNotHasKey(1, $routeInfo,
|
|
||||||
'NOT_FOUND result must only contain a single element in the returned info array'
|
|
||||||
);
|
|
||||||
$this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider provideMethodNotAllowedDispatchCases
|
|
||||||
*/
|
|
||||||
public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods)
|
|
||||||
{
|
|
||||||
$dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
|
|
||||||
$routeInfo = $dispatcher->dispatch($method, $uri);
|
|
||||||
$this->assertArrayHasKey(1, $routeInfo,
|
|
||||||
'METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1'
|
|
||||||
);
|
|
||||||
|
|
||||||
list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri);
|
|
||||||
$this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus);
|
|
||||||
$this->assertSame($availableMethods, $methodArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \FastRoute\BadRouteException
|
|
||||||
* @expectedExceptionMessage Cannot use the same placeholder "test" twice
|
|
||||||
*/
|
|
||||||
public function testDuplicateVariableNameError()
|
|
||||||
{
|
|
||||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0');
|
|
||||||
}, $this->generateDispatcherOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \FastRoute\BadRouteException
|
|
||||||
* @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET"
|
|
||||||
*/
|
|
||||||
public function testDuplicateVariableRoute()
|
|
||||||
{
|
|
||||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;)
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler1');
|
|
||||||
}, $this->generateDispatcherOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \FastRoute\BadRouteException
|
|
||||||
* @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET"
|
|
||||||
*/
|
|
||||||
public function testDuplicateStaticRoute()
|
|
||||||
{
|
|
||||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user', 'handler1');
|
|
||||||
}, $this->generateDispatcherOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \FastRoute\BadRouteException
|
|
||||||
* @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET"
|
|
||||||
*/
|
|
||||||
public function testShadowedStaticRoute()
|
|
||||||
{
|
|
||||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user/nikic', 'handler1');
|
|
||||||
}, $this->generateDispatcherOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \FastRoute\BadRouteException
|
|
||||||
* @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group
|
|
||||||
*/
|
|
||||||
public function testCapturing()
|
|
||||||
{
|
|
||||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/{lang:(en|de)}', 'handler0');
|
|
||||||
}, $this->generateDispatcherOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideFoundDispatchCases()
|
|
||||||
{
|
|
||||||
$cases = [];
|
|
||||||
|
|
||||||
// 0 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/resource/123/456';
|
|
||||||
$handler = 'handler0';
|
|
||||||
$argDict = [];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 1 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/handler0', 'handler0');
|
|
||||||
$r->addRoute('GET', '/handler1', 'handler1');
|
|
||||||
$r->addRoute('GET', '/handler2', 'handler2');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/handler2';
|
|
||||||
$handler = 'handler2';
|
|
||||||
$argDict = [];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 2 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler2');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/rdlowrey';
|
|
||||||
$handler = 'handler2';
|
|
||||||
$argDict = ['name' => 'rdlowrey'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 3 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
// reuse $callback from #2
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/12345';
|
|
||||||
$handler = 'handler1';
|
|
||||||
$argDict = ['id' => '12345'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 4 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
// reuse $callback from #3
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/NaN';
|
|
||||||
$handler = 'handler2';
|
|
||||||
$argDict = ['name' => 'NaN'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 5 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
// reuse $callback from #4
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/rdlowrey/12345';
|
|
||||||
$handler = 'handler0';
|
|
||||||
$argDict = ['name' => 'rdlowrey', 'id' => '12345'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 6 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user/12345/extension', 'handler1');
|
|
||||||
$r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/12345.svg';
|
|
||||||
$handler = 'handler2';
|
|
||||||
$argDict = ['id' => '12345', 'extension' => 'svg'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 7 ----- Test GET method fallback on HEAD route miss ------------------------------------>
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1');
|
|
||||||
$r->addRoute('GET', '/static0', 'handler2');
|
|
||||||
$r->addRoute('GET', '/static1', 'handler3');
|
|
||||||
$r->addRoute('HEAD', '/static1', 'handler4');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'HEAD';
|
|
||||||
$uri = '/user/rdlowrey';
|
|
||||||
$handler = 'handler0';
|
|
||||||
$argDict = ['name' => 'rdlowrey'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 8 ----- Test GET method fallback on HEAD route miss ------------------------------------>
|
|
||||||
|
|
||||||
// reuse $callback from #7
|
|
||||||
|
|
||||||
$method = 'HEAD';
|
|
||||||
$uri = '/user/rdlowrey/1234';
|
|
||||||
$handler = 'handler1';
|
|
||||||
$argDict = ['name' => 'rdlowrey', 'id' => '1234'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 9 ----- Test GET method fallback on HEAD route miss ------------------------------------>
|
|
||||||
|
|
||||||
// reuse $callback from #8
|
|
||||||
|
|
||||||
$method = 'HEAD';
|
|
||||||
$uri = '/static0';
|
|
||||||
$handler = 'handler2';
|
|
||||||
$argDict = [];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 10 ---- Test existing HEAD route used if available (no fallback) ----------------------->
|
|
||||||
|
|
||||||
// reuse $callback from #9
|
|
||||||
|
|
||||||
$method = 'HEAD';
|
|
||||||
$uri = '/static1';
|
|
||||||
$handler = 'handler4';
|
|
||||||
$argDict = [];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 11 ---- More specified routes are not shadowed by less specific of another method ------>
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
|
||||||
$r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'POST';
|
|
||||||
$uri = '/user/rdlowrey';
|
|
||||||
$handler = 'handler1';
|
|
||||||
$argDict = ['name' => 'rdlowrey'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 12 ---- Handler of more specific routes is used, if it occurs first -------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
|
||||||
$r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1');
|
|
||||||
$r->addRoute('POST', '/user/{name}', 'handler2');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'POST';
|
|
||||||
$uri = '/user/rdlowrey';
|
|
||||||
$handler = 'handler1';
|
|
||||||
$argDict = ['name' => 'rdlowrey'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 13 ---- Route with constant suffix ----------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user/{name}/edit', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/rdlowrey/edit';
|
|
||||||
$handler = 'handler1';
|
|
||||||
$argDict = ['name' => 'rdlowrey'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
|
||||||
|
|
||||||
// 14 ---- Handle multiple methods with the same handler ---------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost');
|
|
||||||
$r->addRoute(['DELETE'], '/user', 'handlerDelete');
|
|
||||||
$r->addRoute([], '/user', 'handlerNone');
|
|
||||||
};
|
|
||||||
|
|
||||||
$argDict = [];
|
|
||||||
$cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict];
|
|
||||||
$cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict];
|
|
||||||
$cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict];
|
|
||||||
|
|
||||||
// 17 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('POST', '/user.json', 'handler0');
|
|
||||||
$r->addRoute('GET', '/{entity}.json', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']];
|
|
||||||
|
|
||||||
// 18 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '', 'handler0');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['GET', '', $callback, 'handler0', []];
|
|
||||||
|
|
||||||
// 19 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('HEAD', '/a/{foo}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/b/{foo}', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['HEAD', '/b/bar', $callback, 'handler1', ['foo' => 'bar']];
|
|
||||||
|
|
||||||
// 20 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('HEAD', '/a', 'handler0');
|
|
||||||
$r->addRoute('GET', '/b', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['HEAD', '/b', $callback, 'handler1', []];
|
|
||||||
|
|
||||||
// 21 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/foo', 'handler0');
|
|
||||||
$r->addRoute('HEAD', '/{bar}', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['HEAD', '/foo', $callback, 'handler1', ['bar' => 'foo']];
|
|
||||||
|
|
||||||
// 22 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('*', '/user', 'handler0');
|
|
||||||
$r->addRoute('*', '/{user}', 'handler1');
|
|
||||||
$r->addRoute('GET', '/user', 'handler2');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['GET', '/user', $callback, 'handler2', []];
|
|
||||||
|
|
||||||
// 23 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('*', '/user', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['POST', '/user', $callback, 'handler0', []];
|
|
||||||
|
|
||||||
// 24 ----
|
|
||||||
|
|
||||||
$cases[] = ['HEAD', '/user', $callback, 'handler1', []];
|
|
||||||
|
|
||||||
// 25 ----
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/{bar}', 'handler0');
|
|
||||||
$r->addRoute('*', '/foo', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['GET', '/foo', $callback, 'handler0', ['bar' => 'foo']];
|
|
||||||
|
|
||||||
// 26 ----
|
|
||||||
|
|
||||||
$callback = function(RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user', 'handler0');
|
|
||||||
$r->addRoute('*', '/{foo:.*}', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['POST', '/bar', $callback, 'handler1', ['foo' => 'bar']];
|
|
||||||
|
|
||||||
// x -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
return $cases;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideNotFoundDispatchCases()
|
|
||||||
{
|
|
||||||
$cases = [];
|
|
||||||
|
|
||||||
// 0 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/not-found';
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback];
|
|
||||||
|
|
||||||
// 1 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
// reuse callback from #0
|
|
||||||
$method = 'POST';
|
|
||||||
$uri = '/not-found';
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback];
|
|
||||||
|
|
||||||
// 2 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
// reuse callback from #1
|
|
||||||
$method = 'PUT';
|
|
||||||
$uri = '/not-found';
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback];
|
|
||||||
|
|
||||||
// 3 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/handler0', 'handler0');
|
|
||||||
$r->addRoute('GET', '/handler1', 'handler1');
|
|
||||||
$r->addRoute('GET', '/handler2', 'handler2');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/not-found';
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback];
|
|
||||||
|
|
||||||
// 4 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
|
||||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
|
|
||||||
$r->addRoute('GET', '/user/{name}', 'handler2');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/not-found';
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback];
|
|
||||||
|
|
||||||
// 5 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
// reuse callback from #4
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/rdlowrey/12345/not-found';
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback];
|
|
||||||
|
|
||||||
// 6 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
// reuse callback from #5
|
|
||||||
$method = 'HEAD';
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback];
|
|
||||||
|
|
||||||
// x -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
return $cases;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideMethodNotAllowedDispatchCases()
|
|
||||||
{
|
|
||||||
$cases = [];
|
|
||||||
|
|
||||||
// 0 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'POST';
|
|
||||||
$uri = '/resource/123/456';
|
|
||||||
$allowedMethods = ['GET'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
|
||||||
|
|
||||||
// 1 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
|
||||||
$r->addRoute('POST', '/resource/123/456', 'handler1');
|
|
||||||
$r->addRoute('PUT', '/resource/123/456', 'handler2');
|
|
||||||
$r->addRoute('*', '/', 'handler3');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'DELETE';
|
|
||||||
$uri = '/resource/123/456';
|
|
||||||
$allowedMethods = ['GET', 'POST', 'PUT'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
|
||||||
|
|
||||||
// 2 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
|
||||||
$r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1');
|
|
||||||
$r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2');
|
|
||||||
$r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'DELETE';
|
|
||||||
$uri = '/user/rdlowrey/42';
|
|
||||||
$allowedMethods = ['GET', 'POST', 'PUT', 'PATCH'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
|
||||||
|
|
||||||
// 3 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('POST', '/user/{name}', 'handler1');
|
|
||||||
$r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2');
|
|
||||||
$r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3');
|
|
||||||
};
|
|
||||||
|
|
||||||
$method = 'GET';
|
|
||||||
$uri = '/user/rdlowrey';
|
|
||||||
$allowedMethods = ['POST', 'PUT', 'PATCH'];
|
|
||||||
|
|
||||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
|
||||||
|
|
||||||
// 4 -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost');
|
|
||||||
$r->addRoute(['DELETE'], '/user', 'handlerDelete');
|
|
||||||
$r->addRoute([], '/user', 'handlerNone');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']];
|
|
||||||
|
|
||||||
// 5
|
|
||||||
|
|
||||||
$callback = function (RouteCollector $r) {
|
|
||||||
$r->addRoute('POST', '/user.json', 'handler0');
|
|
||||||
$r->addRoute('GET', '/{entity}.json', 'handler1');
|
|
||||||
};
|
|
||||||
|
|
||||||
$cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']];
|
|
||||||
|
|
||||||
// x -------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
return $cases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class GroupCountBasedTest extends DispatcherTest
|
|
||||||
{
|
|
||||||
protected function getDispatcherClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\Dispatcher\\GroupCountBased';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDataGeneratorClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\DataGenerator\\GroupCountBased';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class GroupPosBasedTest extends DispatcherTest
|
|
||||||
{
|
|
||||||
protected function getDispatcherClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\Dispatcher\\GroupPosBased';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDataGeneratorClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\DataGenerator\\GroupPosBased';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\Dispatcher;
|
|
||||||
|
|
||||||
class MarkBasedTest extends DispatcherTest
|
|
||||||
{
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
preg_match('/(*MARK:A)a/', 'a', $matches);
|
|
||||||
if (!isset($matches['MARK'])) {
|
|
||||||
$this->markTestSkipped('PHP 5.6 required for MARK support');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDispatcherClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\Dispatcher\\MarkBased';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDataGeneratorClass()
|
|
||||||
{
|
|
||||||
return 'FastRoute\\DataGenerator\\MarkBased';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class HackTypecheckerTest extends TestCase
|
|
||||||
{
|
|
||||||
const SERVER_ALREADY_RUNNING_CODE = 77;
|
|
||||||
|
|
||||||
public function testTypechecks($recurse = true)
|
|
||||||
{
|
|
||||||
if (!defined('HHVM_VERSION')) {
|
|
||||||
$this->markTestSkipped('HHVM only');
|
|
||||||
}
|
|
||||||
if (!version_compare(HHVM_VERSION, '3.9.0', '>=')) {
|
|
||||||
$this->markTestSkipped('classname<T> requires HHVM 3.9+');
|
|
||||||
}
|
|
||||||
|
|
||||||
// The typechecker recurses the whole tree, so it makes sure
|
|
||||||
// that everything in fixtures/ is valid when this runs.
|
|
||||||
|
|
||||||
$output = [];
|
|
||||||
$exit_code = null;
|
|
||||||
exec(
|
|
||||||
'hh_server --check ' . escapeshellarg(__DIR__ . '/../../') . ' 2>&1',
|
|
||||||
$output,
|
|
||||||
$exit_code
|
|
||||||
);
|
|
||||||
if ($exit_code === self::SERVER_ALREADY_RUNNING_CODE) {
|
|
||||||
$this->assertTrue(
|
|
||||||
$recurse,
|
|
||||||
'Typechecker still running after running hh_client stop'
|
|
||||||
);
|
|
||||||
// Server already running - 3.10 => 3.11 regression:
|
|
||||||
// https://github.com/facebook/hhvm/issues/6646
|
|
||||||
exec('hh_client stop 2>/dev/null');
|
|
||||||
$this->testTypechecks(/* recurse = */ false);
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
$this->assertSame(0, $exit_code, implode("\n", $output));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?hh
|
|
||||||
|
|
||||||
namespace FastRoute\TestFixtures;
|
|
||||||
|
|
||||||
function all_options_simple(): \FastRoute\Dispatcher {
|
|
||||||
return \FastRoute\simpleDispatcher(
|
|
||||||
$collector ==> {},
|
|
||||||
shape(
|
|
||||||
'routeParser' => \FastRoute\RouteParser\Std::class,
|
|
||||||
'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class,
|
|
||||||
'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class,
|
|
||||||
'routeCollector' => \FastRoute\RouteCollector::class,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function all_options_cached(): \FastRoute\Dispatcher {
|
|
||||||
return \FastRoute\cachedDispatcher(
|
|
||||||
$collector ==> {},
|
|
||||||
shape(
|
|
||||||
'routeParser' => \FastRoute\RouteParser\Std::class,
|
|
||||||
'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class,
|
|
||||||
'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class,
|
|
||||||
'routeCollector' => \FastRoute\RouteCollector::class,
|
|
||||||
'cacheFile' => '/dev/null',
|
|
||||||
'cacheDisabled' => false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?hh
|
|
||||||
|
|
||||||
namespace FastRoute\TestFixtures;
|
|
||||||
|
|
||||||
function empty_options_simple(): \FastRoute\Dispatcher {
|
|
||||||
return \FastRoute\simpleDispatcher($collector ==> {}, shape());
|
|
||||||
}
|
|
||||||
|
|
||||||
function empty_options_cached(): \FastRoute\Dispatcher {
|
|
||||||
return \FastRoute\cachedDispatcher($collector ==> {}, shape());
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?hh
|
|
||||||
|
|
||||||
namespace FastRoute\TestFixtures;
|
|
||||||
|
|
||||||
function no_options_simple(): \FastRoute\Dispatcher {
|
|
||||||
return \FastRoute\simpleDispatcher($collector ==> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function no_options_cached(): \FastRoute\Dispatcher {
|
|
||||||
return \FastRoute\cachedDispatcher($collector ==> {});
|
|
||||||
}
|
|
||||||
108
vendor/nikic/fast-route/test/RouteCollectorTest.php
vendored
108
vendor/nikic/fast-route/test/RouteCollectorTest.php
vendored
@@ -1,108 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class RouteCollectorTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testShortcuts()
|
|
||||||
{
|
|
||||||
$r = new DummyRouteCollector();
|
|
||||||
|
|
||||||
$r->delete('/delete', 'delete');
|
|
||||||
$r->get('/get', 'get');
|
|
||||||
$r->head('/head', 'head');
|
|
||||||
$r->patch('/patch', 'patch');
|
|
||||||
$r->post('/post', 'post');
|
|
||||||
$r->put('/put', 'put');
|
|
||||||
|
|
||||||
$expected = [
|
|
||||||
['DELETE', '/delete', 'delete'],
|
|
||||||
['GET', '/get', 'get'],
|
|
||||||
['HEAD', '/head', 'head'],
|
|
||||||
['PATCH', '/patch', 'patch'],
|
|
||||||
['POST', '/post', 'post'],
|
|
||||||
['PUT', '/put', 'put'],
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($expected, $r->routes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGroups()
|
|
||||||
{
|
|
||||||
$r = new DummyRouteCollector();
|
|
||||||
|
|
||||||
$r->delete('/delete', 'delete');
|
|
||||||
$r->get('/get', 'get');
|
|
||||||
$r->head('/head', 'head');
|
|
||||||
$r->patch('/patch', 'patch');
|
|
||||||
$r->post('/post', 'post');
|
|
||||||
$r->put('/put', 'put');
|
|
||||||
|
|
||||||
$r->addGroup('/group-one', function (DummyRouteCollector $r) {
|
|
||||||
$r->delete('/delete', 'delete');
|
|
||||||
$r->get('/get', 'get');
|
|
||||||
$r->head('/head', 'head');
|
|
||||||
$r->patch('/patch', 'patch');
|
|
||||||
$r->post('/post', 'post');
|
|
||||||
$r->put('/put', 'put');
|
|
||||||
|
|
||||||
$r->addGroup('/group-two', function (DummyRouteCollector $r) {
|
|
||||||
$r->delete('/delete', 'delete');
|
|
||||||
$r->get('/get', 'get');
|
|
||||||
$r->head('/head', 'head');
|
|
||||||
$r->patch('/patch', 'patch');
|
|
||||||
$r->post('/post', 'post');
|
|
||||||
$r->put('/put', 'put');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$r->addGroup('/admin', function (DummyRouteCollector $r) {
|
|
||||||
$r->get('-some-info', 'admin-some-info');
|
|
||||||
});
|
|
||||||
$r->addGroup('/admin-', function (DummyRouteCollector $r) {
|
|
||||||
$r->get('more-info', 'admin-more-info');
|
|
||||||
});
|
|
||||||
|
|
||||||
$expected = [
|
|
||||||
['DELETE', '/delete', 'delete'],
|
|
||||||
['GET', '/get', 'get'],
|
|
||||||
['HEAD', '/head', 'head'],
|
|
||||||
['PATCH', '/patch', 'patch'],
|
|
||||||
['POST', '/post', 'post'],
|
|
||||||
['PUT', '/put', 'put'],
|
|
||||||
['DELETE', '/group-one/delete', 'delete'],
|
|
||||||
['GET', '/group-one/get', 'get'],
|
|
||||||
['HEAD', '/group-one/head', 'head'],
|
|
||||||
['PATCH', '/group-one/patch', 'patch'],
|
|
||||||
['POST', '/group-one/post', 'post'],
|
|
||||||
['PUT', '/group-one/put', 'put'],
|
|
||||||
['DELETE', '/group-one/group-two/delete', 'delete'],
|
|
||||||
['GET', '/group-one/group-two/get', 'get'],
|
|
||||||
['HEAD', '/group-one/group-two/head', 'head'],
|
|
||||||
['PATCH', '/group-one/group-two/patch', 'patch'],
|
|
||||||
['POST', '/group-one/group-two/post', 'post'],
|
|
||||||
['PUT', '/group-one/group-two/put', 'put'],
|
|
||||||
['GET', '/admin-some-info', 'admin-some-info'],
|
|
||||||
['GET', '/admin-more-info', 'admin-more-info'],
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($expected, $r->routes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DummyRouteCollector extends RouteCollector
|
|
||||||
{
|
|
||||||
public $routes = [];
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addRoute($method, $route, $handler)
|
|
||||||
{
|
|
||||||
$route = $this->currentGroupPrefix . $route;
|
|
||||||
$this->routes[] = [$method, $route, $handler];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
154
vendor/nikic/fast-route/test/RouteParser/StdTest.php
vendored
154
vendor/nikic/fast-route/test/RouteParser/StdTest.php
vendored
@@ -1,154 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace FastRoute\RouteParser;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class StdTest extends TestCase
|
|
||||||
{
|
|
||||||
/** @dataProvider provideTestParse */
|
|
||||||
public function testParse($routeString, $expectedRouteDatas)
|
|
||||||
{
|
|
||||||
$parser = new Std();
|
|
||||||
$routeDatas = $parser->parse($routeString);
|
|
||||||
$this->assertSame($expectedRouteDatas, $routeDatas);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @dataProvider provideTestParseError */
|
|
||||||
public function testParseError($routeString, $expectedExceptionMessage)
|
|
||||||
{
|
|
||||||
$parser = new Std();
|
|
||||||
$this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage);
|
|
||||||
$parser->parse($routeString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideTestParse()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'/test',
|
|
||||||
[
|
|
||||||
['/test'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test/{param}',
|
|
||||||
[
|
|
||||||
['/test/', ['param', '[^/]+']],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/te{ param }st',
|
|
||||||
[
|
|
||||||
['/te', ['param', '[^/]+'], 'st']
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test/{param1}/test2/{param2}',
|
|
||||||
[
|
|
||||||
['/test/', ['param1', '[^/]+'], '/test2/', ['param2', '[^/]+']]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test/{param:\d+}',
|
|
||||||
[
|
|
||||||
['/test/', ['param', '\d+']]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test/{ param : \d{1,9} }',
|
|
||||||
[
|
|
||||||
['/test/', ['param', '\d{1,9}']]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test[opt]',
|
|
||||||
[
|
|
||||||
['/test'],
|
|
||||||
['/testopt'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test[/{param}]',
|
|
||||||
[
|
|
||||||
['/test'],
|
|
||||||
['/test/', ['param', '[^/]+']],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/{param}[opt]',
|
|
||||||
[
|
|
||||||
['/', ['param', '[^/]+']],
|
|
||||||
['/', ['param', '[^/]+'], 'opt']
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test[/{name}[/{id:[0-9]+}]]',
|
|
||||||
[
|
|
||||||
['/test'],
|
|
||||||
['/test/', ['name', '[^/]+']],
|
|
||||||
['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'',
|
|
||||||
[
|
|
||||||
[''],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'[test]',
|
|
||||||
[
|
|
||||||
[''],
|
|
||||||
['test'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/{foo-bar}',
|
|
||||||
[
|
|
||||||
['/', ['foo-bar', '[^/]+']]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/{_foo:.*}',
|
|
||||||
[
|
|
||||||
['/', ['_foo', '.*']]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideTestParseError()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'/test[opt',
|
|
||||||
"Number of opening '[' and closing ']' does not match"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test[opt[opt2]',
|
|
||||||
"Number of opening '[' and closing ']' does not match"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/testopt]',
|
|
||||||
"Number of opening '[' and closing ']' does not match"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test[]',
|
|
||||||
'Empty optional part'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test[[opt]]',
|
|
||||||
'Empty optional part'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'[[test]]',
|
|
||||||
'Empty optional part'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'/test[/opt]/required',
|
|
||||||
'Optional segments can only occur at the end of a route'
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
vendor/nikic/fast-route/test/bootstrap.php
vendored
11
vendor/nikic/fast-route/test/bootstrap.php
vendored
@@ -1,11 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../src/functions.php';
|
|
||||||
|
|
||||||
spl_autoload_register(function ($class) {
|
|
||||||
if (strpos($class, 'FastRoute\\') === 0) {
|
|
||||||
$dir = strcasecmp(substr($class, -4), 'Test') ? 'src/' : 'test/';
|
|
||||||
$name = substr($class, strlen('FastRoute'));
|
|
||||||
require __DIR__ . '/../' . $dir . strtr($name, '\\', DIRECTORY_SEPARATOR) . '.php';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
36
vendor/psr/http-message/CHANGELOG.md
vendored
36
vendor/psr/http-message/CHANGELOG.md
vendored
@@ -1,36 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file, in reverse chronological order by release.
|
|
||||||
|
|
||||||
## 1.0.1 - 2016-08-06
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Nothing.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Updated all `@return self` annotation references in interfaces to use
|
|
||||||
`@return static`, which more closelly follows the semantics of the
|
|
||||||
specification.
|
|
||||||
- Updated the `MessageInterface::getHeaders()` return annotation to use the
|
|
||||||
value `string[][]`, indicating the format is a nested array of strings.
|
|
||||||
- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
|
|
||||||
to point to the correct section of RFC 7230.
|
|
||||||
- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
|
|
||||||
to add the parameter name (`$uploadedFiles`).
|
|
||||||
- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
|
|
||||||
method to correctly reference the method parameter (it was referencing an
|
|
||||||
incorrect parameter name previously).
|
|
||||||
|
|
||||||
## 1.0.0 - 2016-05-18
|
|
||||||
|
|
||||||
Initial stable release; reflects accepted PSR-7 specification.
|
|
||||||
19
vendor/psr/http-message/LICENSE
vendored
19
vendor/psr/http-message/LICENSE
vendored
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2014 PHP Framework Interoperability Group
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
16
vendor/psr/http-message/README.md
vendored
16
vendor/psr/http-message/README.md
vendored
@@ -1,16 +0,0 @@
|
|||||||
PSR Http Message
|
|
||||||
================
|
|
||||||
|
|
||||||
This repository holds all interfaces/classes/traits related to
|
|
||||||
[PSR-7](http://www.php-fig.org/psr/psr-7/).
|
|
||||||
|
|
||||||
Note that this is not a HTTP message implementation of its own. It is merely an
|
|
||||||
interface that describes a HTTP message. See the specification for more details.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Before reading the usage guide we recommend reading the PSR-7 interfaces method list:
|
|
||||||
|
|
||||||
* [`PSR-7 Interfaces Method List`](docs/PSR7-Interfaces.md)
|
|
||||||
* [`PSR-7 Usage Guide`](docs/PSR7-Usage.md)
|
|
||||||
26
vendor/psr/http-message/composer.json
vendored
26
vendor/psr/http-message/composer.json
vendored
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "psr/http-message",
|
|
||||||
"description": "Common interface for HTTP messages",
|
|
||||||
"keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
|
|
||||||
"homepage": "https://github.com/php-fig/http-message",
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "PHP-FIG",
|
|
||||||
"homepage": "http://www.php-fig.org/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": "^7.2 || ^8.0"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Psr\\Http\\Message\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.1.x-dev"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
130
vendor/psr/http-message/docs/PSR7-Interfaces.md
vendored
130
vendor/psr/http-message/docs/PSR7-Interfaces.md
vendored
@@ -1,130 +0,0 @@
|
|||||||
# Interfaces
|
|
||||||
|
|
||||||
The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces.
|
|
||||||
|
|
||||||
The interfaces defined in PSR-7 are the following:
|
|
||||||
|
|
||||||
| Class Name | Description |
|
|
||||||
|---|---|
|
|
||||||
| [Psr\Http\Message\MessageInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagemessageinterface) | Representation of a HTTP message |
|
|
||||||
| [Psr\Http\Message\RequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagerequestinterface) | Representation of an outgoing, client-side request. |
|
|
||||||
| [Psr\Http\Message\ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageserverrequestinterface) | Representation of an incoming, server-side HTTP request. |
|
|
||||||
| [Psr\Http\Message\ResponseInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageresponseinterface) | Representation of an outgoing, server-side response. |
|
|
||||||
| [Psr\Http\Message\StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface) | Describes a data stream |
|
|
||||||
| [Psr\Http\Message\UriInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuriinterface) | Value object representing a URI. |
|
|
||||||
| [Psr\Http\Message\UploadedFileInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuploadedfileinterface) | Value object representing a file uploaded through an HTTP request. |
|
|
||||||
|
|
||||||
## `Psr\Http\Message\MessageInterface` Methods
|
|
||||||
|
|
||||||
| Method Name | Description | Notes |
|
|
||||||
|------------------------------------| ----------- | ----- |
|
|
||||||
| `getProtocolVersion()` | Retrieve HTTP protocol version | 1.0 or 1.1 |
|
|
||||||
| `withProtocolVersion($version)` | Returns new message instance with given HTTP protocol version | |
|
|
||||||
| `getHeaders()` | Retrieve all HTTP Headers | [Request Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields), [Response Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) |
|
|
||||||
| `hasHeader($name)` | Checks if HTTP Header with given name exists | |
|
|
||||||
| `getHeader($name)` | Retrieves a array with the values for a single header | |
|
|
||||||
| `getHeaderLine($name)` | Retrieves a comma-separated string of the values for a single header | |
|
|
||||||
| `withHeader($name, $value)` | Returns new message instance with given HTTP Header | if the header existed in the original instance, replaces the header value from the original message with the value provided when creating the new instance. |
|
|
||||||
| `withAddedHeader($name, $value)` | Returns new message instance with appended value to given header | If header already exists value will be appended, if not a new header will be created |
|
|
||||||
| `withoutHeader($name)` | Removes HTTP Header with given name| |
|
|
||||||
| `getBody()` | Retrieves the HTTP Message Body | Returns object implementing `StreamInterface`|
|
|
||||||
| `withBody(StreamInterface $body)` | Returns new message instance with given HTTP Message Body | |
|
|
||||||
|
|
||||||
|
|
||||||
## `Psr\Http\Message\RequestInterface` Methods
|
|
||||||
|
|
||||||
Same methods as `Psr\Http\Message\MessageInterface` + the following methods:
|
|
||||||
|
|
||||||
| Method Name | Description | Notes |
|
|
||||||
|------------------------------------| ----------- | ----- |
|
|
||||||
| `getRequestTarget()` | Retrieves the message's request target | origin-form, absolute-form, authority-form, asterisk-form ([RFC7230](https://www.rfc-editor.org/rfc/rfc7230.txt)) |
|
|
||||||
| `withRequestTarget($requestTarget)` | Return a new message instance with the specific request-target | |
|
|
||||||
| `getMethod()` | Retrieves the HTTP method of the request. | GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE (defined in [RFC7231](https://tools.ietf.org/html/rfc7231)), PATCH (defined in [RFC5789](https://tools.ietf.org/html/rfc5789)) |
|
|
||||||
| `withMethod($method)` | Returns a new message instance with the provided HTTP method | |
|
|
||||||
| `getUri()` | Retrieves the URI instance | |
|
|
||||||
| `withUri(UriInterface $uri, $preserveHost = false)` | Returns a new message instance with the provided URI | |
|
|
||||||
|
|
||||||
|
|
||||||
## `Psr\Http\Message\ServerRequestInterface` Methods
|
|
||||||
|
|
||||||
Same methods as `Psr\Http\Message\RequestInterface` + the following methods:
|
|
||||||
|
|
||||||
| Method Name | Description | Notes |
|
|
||||||
|------------------------------------| ----------- | ----- |
|
|
||||||
| `getServerParams() ` | Retrieve server parameters | Typically derived from `$_SERVER` |
|
|
||||||
| `getCookieParams()` | Retrieves cookies sent by the client to the server. | Typically derived from `$_COOKIES` |
|
|
||||||
| `withCookieParams(array $cookies)` | Returns a new request instance with the specified cookies | |
|
|
||||||
| `withQueryParams(array $query)` | Returns a new request instance with the specified query string arguments | |
|
|
||||||
| `getUploadedFiles()` | Retrieve normalized file upload data | |
|
|
||||||
| `withUploadedFiles(array $uploadedFiles)` | Returns a new request instance with the specified uploaded files | |
|
|
||||||
| `getParsedBody()` | Retrieve any parameters provided in the request body | |
|
|
||||||
| `withParsedBody($data)` | Returns a new request instance with the specified body parameters | |
|
|
||||||
| `getAttributes()` | Retrieve attributes derived from the request | |
|
|
||||||
| `getAttribute($name, $default = null)` | Retrieve a single derived request attribute | |
|
|
||||||
| `withAttribute($name, $value)` | Returns a new request instance with the specified derived request attribute | |
|
|
||||||
| `withoutAttribute($name)` | Returns a new request instance that without the specified derived request attribute | |
|
|
||||||
|
|
||||||
## `Psr\Http\Message\ResponseInterface` Methods:
|
|
||||||
|
|
||||||
Same methods as `Psr\Http\Message\MessageInterface` + the following methods:
|
|
||||||
|
|
||||||
| Method Name | Description | Notes |
|
|
||||||
|------------------------------------| ----------- | ----- |
|
|
||||||
| `getStatusCode()` | Gets the response status code. | |
|
|
||||||
| `withStatus($code, $reasonPhrase = '')` | Returns a new response instance with the specified status code and, optionally, reason phrase. | |
|
|
||||||
| `getReasonPhrase()` | Gets the response reason phrase associated with the status code. | |
|
|
||||||
|
|
||||||
## `Psr\Http\Message\StreamInterface` Methods
|
|
||||||
|
|
||||||
| Method Name | Description | Notes |
|
|
||||||
|------------------------------------| ----------- | ----- |
|
|
||||||
| `__toString()` | Reads all data from the stream into a string, from the beginning to end. | |
|
|
||||||
| `close()` | Closes the stream and any underlying resources. | |
|
|
||||||
| `detach()` | Separates any underlying resources from the stream. | |
|
|
||||||
| `getSize()` | Get the size of the stream if known. | |
|
|
||||||
| `eof()` | Returns true if the stream is at the end of the stream.| |
|
|
||||||
| `isSeekable()` | Returns whether or not the stream is seekable. | |
|
|
||||||
| `seek($offset, $whence = SEEK_SET)` | Seek to a position in the stream. | |
|
|
||||||
| `rewind()` | Seek to the beginning of the stream. | |
|
|
||||||
| `isWritable()` | Returns whether or not the stream is writable. | |
|
|
||||||
| `write($string)` | Write data to the stream. | |
|
|
||||||
| `isReadable()` | Returns whether or not the stream is readable. | |
|
|
||||||
| `read($length)` | Read data from the stream. | |
|
|
||||||
| `getContents()` | Returns the remaining contents in a string | |
|
|
||||||
| `getMetadata($key = null)()` | Get stream metadata as an associative array or retrieve a specific key. | |
|
|
||||||
|
|
||||||
## `Psr\Http\Message\UriInterface` Methods
|
|
||||||
|
|
||||||
| Method Name | Description | Notes |
|
|
||||||
|------------------------------------| ----------- | ----- |
|
|
||||||
| `getScheme()` | Retrieve the scheme component of the URI. | |
|
|
||||||
| `getAuthority()` | Retrieve the authority component of the URI. | |
|
|
||||||
| `getUserInfo()` | Retrieve the user information component of the URI. | |
|
|
||||||
| `getHost()` | Retrieve the host component of the URI. | |
|
|
||||||
| `getPort()` | Retrieve the port component of the URI. | |
|
|
||||||
| `getPath()` | Retrieve the path component of the URI. | |
|
|
||||||
| `getQuery()` | Retrieve the query string of the URI. | |
|
|
||||||
| `getFragment()` | Retrieve the fragment component of the URI. | |
|
|
||||||
| `withScheme($scheme)` | Return an instance with the specified scheme. | |
|
|
||||||
| `withUserInfo($user, $password = null)` | Return an instance with the specified user information. | |
|
|
||||||
| `withHost($host)` | Return an instance with the specified host. | |
|
|
||||||
| `withPort($port)` | Return an instance with the specified port. | |
|
|
||||||
| `withPath($path)` | Return an instance with the specified path. | |
|
|
||||||
| `withQuery($query)` | Return an instance with the specified query string. | |
|
|
||||||
| `withFragment($fragment)` | Return an instance with the specified URI fragment. | |
|
|
||||||
| `__toString()` | Return the string representation as a URI reference. | |
|
|
||||||
|
|
||||||
## `Psr\Http\Message\UploadedFileInterface` Methods
|
|
||||||
|
|
||||||
| Method Name | Description | Notes |
|
|
||||||
|------------------------------------| ----------- | ----- |
|
|
||||||
| `getStream()` | Retrieve a stream representing the uploaded file. | |
|
|
||||||
| `moveTo($targetPath)` | Move the uploaded file to a new location. | |
|
|
||||||
| `getSize()` | Retrieve the file size. | |
|
|
||||||
| `getError()` | Retrieve the error associated with the uploaded file. | |
|
|
||||||
| `getClientFilename()` | Retrieve the filename sent by the client. | |
|
|
||||||
| `getClientMediaType()` | Retrieve the media type sent by the client. | |
|
|
||||||
|
|
||||||
> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`.
|
|
||||||
> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
|
|
||||||
|
|
||||||
159
vendor/psr/http-message/docs/PSR7-Usage.md
vendored
159
vendor/psr/http-message/docs/PSR7-Usage.md
vendored
@@ -1,159 +0,0 @@
|
|||||||
### PSR-7 Usage
|
|
||||||
|
|
||||||
All PSR-7 applications comply with these interfaces
|
|
||||||
They were created to establish a standard between middleware implementations.
|
|
||||||
|
|
||||||
> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`.
|
|
||||||
> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
|
|
||||||
|
|
||||||
|
|
||||||
The following examples will illustrate how basic operations are done in PSR-7.
|
|
||||||
|
|
||||||
##### Examples
|
|
||||||
|
|
||||||
|
|
||||||
For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc)
|
|
||||||
All PSR-7 implementations should have the same behaviour.
|
|
||||||
|
|
||||||
The following will be assumed:
|
|
||||||
`$request` is an object of `Psr\Http\Message\RequestInterface` and
|
|
||||||
|
|
||||||
`$response` is an object implementing `Psr\Http\Message\RequestInterface`
|
|
||||||
|
|
||||||
|
|
||||||
### Working with HTTP Headers
|
|
||||||
|
|
||||||
#### Adding headers to response:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$response->withHeader('My-Custom-Header', 'My Custom Message');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Appending values to headers
|
|
||||||
|
|
||||||
```php
|
|
||||||
$response->withAddedHeader('My-Custom-Header', 'The second message');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Checking if header exists:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$request->hasHeader('My-Custom-Header'); // will return false
|
|
||||||
$response->hasHeader('My-Custom-Header'); // will return true
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note: My-Custom-Header was only added in the Response
|
|
||||||
|
|
||||||
#### Getting comma-separated values from a header (also applies to request)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// getting value from request headers
|
|
||||||
$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8"
|
|
||||||
// getting value from response headers
|
|
||||||
$response->getHeaderLine('My-Custom-Header'); // will return: "My Custom Message; The second message"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Getting array of value from a header (also applies to request)
|
|
||||||
```php
|
|
||||||
// getting value from request headers
|
|
||||||
$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"]
|
|
||||||
// getting value from response headers
|
|
||||||
$response->getHeader('My-Custom-Header'); // will return: ["My Custom Message", "The second message"]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Removing headers from HTTP Messages
|
|
||||||
```php
|
|
||||||
// removing a header from Request, removing deprecated "Content-MD5" header
|
|
||||||
$request->withoutHeader('Content-MD5');
|
|
||||||
|
|
||||||
// removing a header from Response
|
|
||||||
// effect: the browser won't know the size of the stream
|
|
||||||
// the browser will download the stream till it ends
|
|
||||||
$response->withoutHeader('Content-Length');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with HTTP Message Body
|
|
||||||
|
|
||||||
When working with the PSR-7 there are two methods of implementation:
|
|
||||||
#### 1. Getting the body separately
|
|
||||||
|
|
||||||
> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$body = $response->getBody();
|
|
||||||
// operations on body, eg. read, write, seek
|
|
||||||
// ...
|
|
||||||
// replacing the old body
|
|
||||||
$response->withBody($body);
|
|
||||||
// this last statement is optional as we working with objects
|
|
||||||
// in this case the "new" body is same with the "old" one
|
|
||||||
// the $body variable has the same value as the one in $request, only the reference is passed
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Working directly on response
|
|
||||||
|
|
||||||
> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required
|
|
||||||
|
|
||||||
```php
|
|
||||||
$response->getBody()->write('hello');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Getting the body contents
|
|
||||||
|
|
||||||
The following snippet gets the contents of a stream contents.
|
|
||||||
> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream.
|
|
||||||
```php
|
|
||||||
$body = $response->getBody();
|
|
||||||
$body->rewind(); // or $body->seek(0);
|
|
||||||
$bodyText = $body->getContents();
|
|
||||||
```
|
|
||||||
> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended.
|
|
||||||
|
|
||||||
### Append to body
|
|
||||||
|
|
||||||
```php
|
|
||||||
$response->getBody()->write('Hello'); // writing directly
|
|
||||||
$body = $request->getBody(); // which is a `StreamInterface`
|
|
||||||
$body->write('xxxxx');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Prepend to body
|
|
||||||
Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended.
|
|
||||||
The following example will explain the behaviour of streams.
|
|
||||||
|
|
||||||
```php
|
|
||||||
// assuming our response is initially empty
|
|
||||||
$body = $repsonse->getBody();
|
|
||||||
// writing the string "abcd"
|
|
||||||
$body->write('abcd');
|
|
||||||
|
|
||||||
// seeking to start of stream
|
|
||||||
$body->seek(0);
|
|
||||||
// writing 'ef'
|
|
||||||
$body->write('ef'); // at this point the stream contains "efcd"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Prepending by rewriting separately
|
|
||||||
|
|
||||||
```php
|
|
||||||
// assuming our response body stream only contains: "abcd"
|
|
||||||
$body = $response->getBody();
|
|
||||||
$body->rewind();
|
|
||||||
$contents = $body->getContents(); // abcd
|
|
||||||
// seeking the stream to beginning
|
|
||||||
$body->rewind();
|
|
||||||
$body->write('ef'); // stream contains "efcd"
|
|
||||||
$body->write($contents); // stream contains "efabcd"
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`.
|
|
||||||
|
|
||||||
#### Prepending by using contents as a string
|
|
||||||
```php
|
|
||||||
$body = $response->getBody();
|
|
||||||
$body->rewind();
|
|
||||||
$contents = $body->getContents(); // efabcd
|
|
||||||
$contents = 'ef'.$contents;
|
|
||||||
$body->rewind();
|
|
||||||
$body->write($contents);
|
|
||||||
```
|
|
||||||
189
vendor/psr/http-message/src/MessageInterface.php
vendored
189
vendor/psr/http-message/src/MessageInterface.php
vendored
@@ -1,189 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Psr\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP messages consist of requests from a client to a server and responses
|
|
||||||
* from a server to a client. This interface defines the methods common to
|
|
||||||
* each.
|
|
||||||
*
|
|
||||||
* Messages are considered immutable; all methods that might change state MUST
|
|
||||||
* be implemented such that they retain the internal state of the current
|
|
||||||
* message and return an instance that contains the changed state.
|
|
||||||
*
|
|
||||||
* @link http://www.ietf.org/rfc/rfc7230.txt
|
|
||||||
* @link http://www.ietf.org/rfc/rfc7231.txt
|
|
||||||
*/
|
|
||||||
interface MessageInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Retrieves the HTTP protocol version as a string.
|
|
||||||
*
|
|
||||||
* The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
|
|
||||||
*
|
|
||||||
* @return string HTTP protocol version.
|
|
||||||
*/
|
|
||||||
public function getProtocolVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified HTTP protocol version.
|
|
||||||
*
|
|
||||||
* The version string MUST contain only the HTTP version number (e.g.,
|
|
||||||
* "1.1", "1.0").
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* new protocol version.
|
|
||||||
*
|
|
||||||
* @param string $version HTTP protocol version
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withProtocolVersion(string $version);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all message header values.
|
|
||||||
*
|
|
||||||
* The keys represent the header name as it will be sent over the wire, and
|
|
||||||
* each value is an array of strings associated with the header.
|
|
||||||
*
|
|
||||||
* // Represent the headers as a string
|
|
||||||
* foreach ($message->getHeaders() as $name => $values) {
|
|
||||||
* echo $name . ": " . implode(", ", $values);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // Emit headers iteratively:
|
|
||||||
* foreach ($message->getHeaders() as $name => $values) {
|
|
||||||
* foreach ($values as $value) {
|
|
||||||
* header(sprintf('%s: %s', $name, $value), false);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* While header names are not case-sensitive, getHeaders() will preserve the
|
|
||||||
* exact case in which headers were originally specified.
|
|
||||||
*
|
|
||||||
* @return string[][] Returns an associative array of the message's headers. Each
|
|
||||||
* key MUST be a header name, and each value MUST be an array of strings
|
|
||||||
* for that header.
|
|
||||||
*/
|
|
||||||
public function getHeaders();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a header exists by the given case-insensitive name.
|
|
||||||
*
|
|
||||||
* @param string $name Case-insensitive header field name.
|
|
||||||
* @return bool Returns true if any header names match the given header
|
|
||||||
* name using a case-insensitive string comparison. Returns false if
|
|
||||||
* no matching header name is found in the message.
|
|
||||||
*/
|
|
||||||
public function hasHeader(string $name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a message header value by the given case-insensitive name.
|
|
||||||
*
|
|
||||||
* This method returns an array of all the header values of the given
|
|
||||||
* case-insensitive header name.
|
|
||||||
*
|
|
||||||
* If the header does not appear in the message, this method MUST return an
|
|
||||||
* empty array.
|
|
||||||
*
|
|
||||||
* @param string $name Case-insensitive header field name.
|
|
||||||
* @return string[] An array of string values as provided for the given
|
|
||||||
* header. If the header does not appear in the message, this method MUST
|
|
||||||
* return an empty array.
|
|
||||||
*/
|
|
||||||
public function getHeader(string $name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a comma-separated string of the values for a single header.
|
|
||||||
*
|
|
||||||
* This method returns all of the header values of the given
|
|
||||||
* case-insensitive header name as a string concatenated together using
|
|
||||||
* a comma.
|
|
||||||
*
|
|
||||||
* NOTE: Not all header values may be appropriately represented using
|
|
||||||
* comma concatenation. For such headers, use getHeader() instead
|
|
||||||
* and supply your own delimiter when concatenating.
|
|
||||||
*
|
|
||||||
* If the header does not appear in the message, this method MUST return
|
|
||||||
* an empty string.
|
|
||||||
*
|
|
||||||
* @param string $name Case-insensitive header field name.
|
|
||||||
* @return string A string of values as provided for the given header
|
|
||||||
* concatenated together using a comma. If the header does not appear in
|
|
||||||
* the message, this method MUST return an empty string.
|
|
||||||
*/
|
|
||||||
public function getHeaderLine(string $name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the provided value replacing the specified header.
|
|
||||||
*
|
|
||||||
* While header names are case-insensitive, the casing of the header will
|
|
||||||
* be preserved by this function, and returned from getHeaders().
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* new and/or updated header and value.
|
|
||||||
*
|
|
||||||
* @param string $name Case-insensitive header field name.
|
|
||||||
* @param string|string[] $value Header value(s).
|
|
||||||
* @return static
|
|
||||||
* @throws \InvalidArgumentException for invalid header names or values.
|
|
||||||
*/
|
|
||||||
public function withHeader(string $name, $value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified header appended with the given value.
|
|
||||||
*
|
|
||||||
* Existing values for the specified header will be maintained. The new
|
|
||||||
* value(s) will be appended to the existing list. If the header did not
|
|
||||||
* exist previously, it will be added.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* new header and/or value.
|
|
||||||
*
|
|
||||||
* @param string $name Case-insensitive header field name to add.
|
|
||||||
* @param string|string[] $value Header value(s).
|
|
||||||
* @return static
|
|
||||||
* @throws \InvalidArgumentException for invalid header names or values.
|
|
||||||
*/
|
|
||||||
public function withAddedHeader(string $name, $value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance without the specified header.
|
|
||||||
*
|
|
||||||
* Header resolution MUST be done without case-sensitivity.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that removes
|
|
||||||
* the named header.
|
|
||||||
*
|
|
||||||
* @param string $name Case-insensitive header field name to remove.
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withoutHeader(string $name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the body of the message.
|
|
||||||
*
|
|
||||||
* @return StreamInterface Returns the body as a stream.
|
|
||||||
*/
|
|
||||||
public function getBody();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified message body.
|
|
||||||
*
|
|
||||||
* The body MUST be a StreamInterface object.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return a new instance that has the
|
|
||||||
* new body stream.
|
|
||||||
*
|
|
||||||
* @param StreamInterface $body Body.
|
|
||||||
* @return static
|
|
||||||
* @throws \InvalidArgumentException When the body is not valid.
|
|
||||||
*/
|
|
||||||
public function withBody(StreamInterface $body);
|
|
||||||
}
|
|
||||||
131
vendor/psr/http-message/src/RequestInterface.php
vendored
131
vendor/psr/http-message/src/RequestInterface.php
vendored
@@ -1,131 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Psr\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representation of an outgoing, client-side request.
|
|
||||||
*
|
|
||||||
* Per the HTTP specification, this interface includes properties for
|
|
||||||
* each of the following:
|
|
||||||
*
|
|
||||||
* - Protocol version
|
|
||||||
* - HTTP method
|
|
||||||
* - URI
|
|
||||||
* - Headers
|
|
||||||
* - Message body
|
|
||||||
*
|
|
||||||
* During construction, implementations MUST attempt to set the Host header from
|
|
||||||
* a provided URI if no Host header is provided.
|
|
||||||
*
|
|
||||||
* Requests are considered immutable; all methods that might change state MUST
|
|
||||||
* be implemented such that they retain the internal state of the current
|
|
||||||
* message and return an instance that contains the changed state.
|
|
||||||
*/
|
|
||||||
interface RequestInterface extends MessageInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Retrieves the message's request target.
|
|
||||||
*
|
|
||||||
* Retrieves the message's request-target either as it will appear (for
|
|
||||||
* clients), as it appeared at request (for servers), or as it was
|
|
||||||
* specified for the instance (see withRequestTarget()).
|
|
||||||
*
|
|
||||||
* In most cases, this will be the origin-form of the composed URI,
|
|
||||||
* unless a value was provided to the concrete implementation (see
|
|
||||||
* withRequestTarget() below).
|
|
||||||
*
|
|
||||||
* If no URI is available, and no request-target has been specifically
|
|
||||||
* provided, this method MUST return the string "/".
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getRequestTarget();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specific request-target.
|
|
||||||
*
|
|
||||||
* If the request needs a non-origin-form request-target — e.g., for
|
|
||||||
* specifying an absolute-form, authority-form, or asterisk-form —
|
|
||||||
* this method may be used to create an instance with the specified
|
|
||||||
* request-target, verbatim.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* changed request target.
|
|
||||||
*
|
|
||||||
* @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
|
|
||||||
* request-target forms allowed in request messages)
|
|
||||||
* @param string $requestTarget
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withRequestTarget(string $requestTarget);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the HTTP method of the request.
|
|
||||||
*
|
|
||||||
* @return string Returns the request method.
|
|
||||||
*/
|
|
||||||
public function getMethod();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the provided HTTP method.
|
|
||||||
*
|
|
||||||
* While HTTP method names are typically all uppercase characters, HTTP
|
|
||||||
* method names are case-sensitive and thus implementations SHOULD NOT
|
|
||||||
* modify the given string.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* changed request method.
|
|
||||||
*
|
|
||||||
* @param string $method Case-sensitive method.
|
|
||||||
* @return static
|
|
||||||
* @throws \InvalidArgumentException for invalid HTTP methods.
|
|
||||||
*/
|
|
||||||
public function withMethod(string $method);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the URI instance.
|
|
||||||
*
|
|
||||||
* This method MUST return a UriInterface instance.
|
|
||||||
*
|
|
||||||
* @link http://tools.ietf.org/html/rfc3986#section-4.3
|
|
||||||
* @return UriInterface Returns a UriInterface instance
|
|
||||||
* representing the URI of the request.
|
|
||||||
*/
|
|
||||||
public function getUri();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an instance with the provided URI.
|
|
||||||
*
|
|
||||||
* This method MUST update the Host header of the returned request by
|
|
||||||
* default if the URI contains a host component. If the URI does not
|
|
||||||
* contain a host component, any pre-existing Host header MUST be carried
|
|
||||||
* over to the returned request.
|
|
||||||
*
|
|
||||||
* You can opt-in to preserving the original state of the Host header by
|
|
||||||
* setting `$preserveHost` to `true`. When `$preserveHost` is set to
|
|
||||||
* `true`, this method interacts with the Host header in the following ways:
|
|
||||||
*
|
|
||||||
* - If the Host header is missing or empty, and the new URI contains
|
|
||||||
* a host component, this method MUST update the Host header in the returned
|
|
||||||
* request.
|
|
||||||
* - If the Host header is missing or empty, and the new URI does not contain a
|
|
||||||
* host component, this method MUST NOT update the Host header in the returned
|
|
||||||
* request.
|
|
||||||
* - If a Host header is present and non-empty, this method MUST NOT update
|
|
||||||
* the Host header in the returned request.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* new UriInterface instance.
|
|
||||||
*
|
|
||||||
* @link http://tools.ietf.org/html/rfc3986#section-4.3
|
|
||||||
* @param UriInterface $uri New request URI to use.
|
|
||||||
* @param bool $preserveHost Preserve the original state of the Host header.
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withUri(UriInterface $uri, bool $preserveHost = false);
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Psr\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representation of an outgoing, server-side response.
|
|
||||||
*
|
|
||||||
* Per the HTTP specification, this interface includes properties for
|
|
||||||
* each of the following:
|
|
||||||
*
|
|
||||||
* - Protocol version
|
|
||||||
* - Status code and reason phrase
|
|
||||||
* - Headers
|
|
||||||
* - Message body
|
|
||||||
*
|
|
||||||
* Responses are considered immutable; all methods that might change state MUST
|
|
||||||
* be implemented such that they retain the internal state of the current
|
|
||||||
* message and return an instance that contains the changed state.
|
|
||||||
*/
|
|
||||||
interface ResponseInterface extends MessageInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Gets the response status code.
|
|
||||||
*
|
|
||||||
* The status code is a 3-digit integer result code of the server's attempt
|
|
||||||
* to understand and satisfy the request.
|
|
||||||
*
|
|
||||||
* @return int Status code.
|
|
||||||
*/
|
|
||||||
public function getStatusCode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified status code and, optionally, reason phrase.
|
|
||||||
*
|
|
||||||
* If no reason phrase is specified, implementations MAY choose to default
|
|
||||||
* to the RFC 7231 or IANA recommended reason phrase for the response's
|
|
||||||
* status code.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* updated status and reason phrase.
|
|
||||||
*
|
|
||||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
|
||||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
|
||||||
* @param int $code The 3-digit integer result code to set.
|
|
||||||
* @param string $reasonPhrase The reason phrase to use with the
|
|
||||||
* provided status code; if none is provided, implementations MAY
|
|
||||||
* use the defaults as suggested in the HTTP specification.
|
|
||||||
* @return static
|
|
||||||
* @throws \InvalidArgumentException For invalid status code arguments.
|
|
||||||
*/
|
|
||||||
public function withStatus(int $code, string $reasonPhrase = '');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the response reason phrase associated with the status code.
|
|
||||||
*
|
|
||||||
* Because a reason phrase is not a required element in a response
|
|
||||||
* status line, the reason phrase value MAY be null. Implementations MAY
|
|
||||||
* choose to return the default RFC 7231 recommended reason phrase (or those
|
|
||||||
* listed in the IANA HTTP Status Code Registry) for the response's
|
|
||||||
* status code.
|
|
||||||
*
|
|
||||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
|
||||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
|
||||||
* @return string Reason phrase; must return an empty string if none present.
|
|
||||||
*/
|
|
||||||
public function getReasonPhrase();
|
|
||||||
}
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Psr\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representation of an incoming, server-side HTTP request.
|
|
||||||
*
|
|
||||||
* Per the HTTP specification, this interface includes properties for
|
|
||||||
* each of the following:
|
|
||||||
*
|
|
||||||
* - Protocol version
|
|
||||||
* - HTTP method
|
|
||||||
* - URI
|
|
||||||
* - Headers
|
|
||||||
* - Message body
|
|
||||||
*
|
|
||||||
* Additionally, it encapsulates all data as it has arrived to the
|
|
||||||
* application from the CGI and/or PHP environment, including:
|
|
||||||
*
|
|
||||||
* - The values represented in $_SERVER.
|
|
||||||
* - Any cookies provided (generally via $_COOKIE)
|
|
||||||
* - Query string arguments (generally via $_GET, or as parsed via parse_str())
|
|
||||||
* - Upload files, if any (as represented by $_FILES)
|
|
||||||
* - Deserialized body parameters (generally from $_POST)
|
|
||||||
*
|
|
||||||
* $_SERVER values MUST be treated as immutable, as they represent application
|
|
||||||
* state at the time of request; as such, no methods are provided to allow
|
|
||||||
* modification of those values. The other values provide such methods, as they
|
|
||||||
* can be restored from $_SERVER or the request body, and may need treatment
|
|
||||||
* during the application (e.g., body parameters may be deserialized based on
|
|
||||||
* content type).
|
|
||||||
*
|
|
||||||
* Additionally, this interface recognizes the utility of introspecting a
|
|
||||||
* request to derive and match additional parameters (e.g., via URI path
|
|
||||||
* matching, decrypting cookie values, deserializing non-form-encoded body
|
|
||||||
* content, matching authorization headers to users, etc). These parameters
|
|
||||||
* are stored in an "attributes" property.
|
|
||||||
*
|
|
||||||
* Requests are considered immutable; all methods that might change state MUST
|
|
||||||
* be implemented such that they retain the internal state of the current
|
|
||||||
* message and return an instance that contains the changed state.
|
|
||||||
*/
|
|
||||||
interface ServerRequestInterface extends RequestInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Retrieve server parameters.
|
|
||||||
*
|
|
||||||
* Retrieves data related to the incoming request environment,
|
|
||||||
* typically derived from PHP's $_SERVER superglobal. The data IS NOT
|
|
||||||
* REQUIRED to originate from $_SERVER.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getServerParams();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve cookies.
|
|
||||||
*
|
|
||||||
* Retrieves cookies sent by the client to the server.
|
|
||||||
*
|
|
||||||
* The data MUST be compatible with the structure of the $_COOKIE
|
|
||||||
* superglobal.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getCookieParams();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified cookies.
|
|
||||||
*
|
|
||||||
* The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
|
|
||||||
* be compatible with the structure of $_COOKIE. Typically, this data will
|
|
||||||
* be injected at instantiation.
|
|
||||||
*
|
|
||||||
* This method MUST NOT update the related Cookie header of the request
|
|
||||||
* instance, nor related values in the server params.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* updated cookie values.
|
|
||||||
*
|
|
||||||
* @param array $cookies Array of key/value pairs representing cookies.
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withCookieParams(array $cookies);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve query string arguments.
|
|
||||||
*
|
|
||||||
* Retrieves the deserialized query string arguments, if any.
|
|
||||||
*
|
|
||||||
* Note: the query params might not be in sync with the URI or server
|
|
||||||
* params. If you need to ensure you are only getting the original
|
|
||||||
* values, you may need to parse the query string from `getUri()->getQuery()`
|
|
||||||
* or from the `QUERY_STRING` server param.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getQueryParams();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified query string arguments.
|
|
||||||
*
|
|
||||||
* These values SHOULD remain immutable over the course of the incoming
|
|
||||||
* request. They MAY be injected during instantiation, such as from PHP's
|
|
||||||
* $_GET superglobal, or MAY be derived from some other value such as the
|
|
||||||
* URI. In cases where the arguments are parsed from the URI, the data
|
|
||||||
* MUST be compatible with what PHP's parse_str() would return for
|
|
||||||
* purposes of how duplicate query parameters are handled, and how nested
|
|
||||||
* sets are handled.
|
|
||||||
*
|
|
||||||
* Setting query string arguments MUST NOT change the URI stored by the
|
|
||||||
* request, nor the values in the server params.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* updated query string arguments.
|
|
||||||
*
|
|
||||||
* @param array $query Array of query string arguments, typically from
|
|
||||||
* $_GET.
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withQueryParams(array $query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve normalized file upload data.
|
|
||||||
*
|
|
||||||
* This method returns upload metadata in a normalized tree, with each leaf
|
|
||||||
* an instance of Psr\Http\Message\UploadedFileInterface.
|
|
||||||
*
|
|
||||||
* These values MAY be prepared from $_FILES or the message body during
|
|
||||||
* instantiation, or MAY be injected via withUploadedFiles().
|
|
||||||
*
|
|
||||||
* @return array An array tree of UploadedFileInterface instances; an empty
|
|
||||||
* array MUST be returned if no data is present.
|
|
||||||
*/
|
|
||||||
public function getUploadedFiles();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new instance with the specified uploaded files.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* updated body parameters.
|
|
||||||
*
|
|
||||||
* @param array $uploadedFiles An array tree of UploadedFileInterface instances.
|
|
||||||
* @return static
|
|
||||||
* @throws \InvalidArgumentException if an invalid structure is provided.
|
|
||||||
*/
|
|
||||||
public function withUploadedFiles(array $uploadedFiles);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve any parameters provided in the request body.
|
|
||||||
*
|
|
||||||
* If the request Content-Type is either application/x-www-form-urlencoded
|
|
||||||
* or multipart/form-data, and the request method is POST, this method MUST
|
|
||||||
* return the contents of $_POST.
|
|
||||||
*
|
|
||||||
* Otherwise, this method may return any results of deserializing
|
|
||||||
* the request body content; as parsing returns structured content, the
|
|
||||||
* potential types MUST be arrays or objects only. A null value indicates
|
|
||||||
* the absence of body content.
|
|
||||||
*
|
|
||||||
* @return null|array|object The deserialized body parameters, if any.
|
|
||||||
* These will typically be an array or object.
|
|
||||||
*/
|
|
||||||
public function getParsedBody();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified body parameters.
|
|
||||||
*
|
|
||||||
* These MAY be injected during instantiation.
|
|
||||||
*
|
|
||||||
* If the request Content-Type is either application/x-www-form-urlencoded
|
|
||||||
* or multipart/form-data, and the request method is POST, use this method
|
|
||||||
* ONLY to inject the contents of $_POST.
|
|
||||||
*
|
|
||||||
* The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
|
|
||||||
* deserializing the request body content. Deserialization/parsing returns
|
|
||||||
* structured data, and, as such, this method ONLY accepts arrays or objects,
|
|
||||||
* or a null value if nothing was available to parse.
|
|
||||||
*
|
|
||||||
* As an example, if content negotiation determines that the request data
|
|
||||||
* is a JSON payload, this method could be used to create a request
|
|
||||||
* instance with the deserialized parameters.
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* updated body parameters.
|
|
||||||
*
|
|
||||||
* @param null|array|object $data The deserialized body data. This will
|
|
||||||
* typically be in an array or object.
|
|
||||||
* @return static
|
|
||||||
* @throws \InvalidArgumentException if an unsupported argument type is
|
|
||||||
* provided.
|
|
||||||
*/
|
|
||||||
public function withParsedBody($data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve attributes derived from the request.
|
|
||||||
*
|
|
||||||
* The request "attributes" may be used to allow injection of any
|
|
||||||
* parameters derived from the request: e.g., the results of path
|
|
||||||
* match operations; the results of decrypting cookies; the results of
|
|
||||||
* deserializing non-form-encoded message bodies; etc. Attributes
|
|
||||||
* will be application and request specific, and CAN be mutable.
|
|
||||||
*
|
|
||||||
* @return array Attributes derived from the request.
|
|
||||||
*/
|
|
||||||
public function getAttributes();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a single derived request attribute.
|
|
||||||
*
|
|
||||||
* Retrieves a single derived request attribute as described in
|
|
||||||
* getAttributes(). If the attribute has not been previously set, returns
|
|
||||||
* the default value as provided.
|
|
||||||
*
|
|
||||||
* This method obviates the need for a hasAttribute() method, as it allows
|
|
||||||
* specifying a default value to return if the attribute is not found.
|
|
||||||
*
|
|
||||||
* @see getAttributes()
|
|
||||||
* @param string $name The attribute name.
|
|
||||||
* @param mixed $default Default value to return if the attribute does not exist.
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getAttribute(string $name, $default = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified derived request attribute.
|
|
||||||
*
|
|
||||||
* This method allows setting a single derived request attribute as
|
|
||||||
* described in getAttributes().
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that has the
|
|
||||||
* updated attribute.
|
|
||||||
*
|
|
||||||
* @see getAttributes()
|
|
||||||
* @param string $name The attribute name.
|
|
||||||
* @param mixed $value The value of the attribute.
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withAttribute(string $name, $value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance that removes the specified derived request attribute.
|
|
||||||
*
|
|
||||||
* This method allows removing a single derived request attribute as
|
|
||||||
* described in getAttributes().
|
|
||||||
*
|
|
||||||
* This method MUST be implemented in such a way as to retain the
|
|
||||||
* immutability of the message, and MUST return an instance that removes
|
|
||||||
* the attribute.
|
|
||||||
*
|
|
||||||
* @see getAttributes()
|
|
||||||
* @param string $name The attribute name.
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function withoutAttribute(string $name);
|
|
||||||
}
|
|
||||||
160
vendor/psr/http-message/src/StreamInterface.php
vendored
160
vendor/psr/http-message/src/StreamInterface.php
vendored
@@ -1,160 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Psr\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes a data stream.
|
|
||||||
*
|
|
||||||
* Typically, an instance will wrap a PHP stream; this interface provides
|
|
||||||
* a wrapper around the most common operations, including serialization of
|
|
||||||
* the entire stream to a string.
|
|
||||||
*/
|
|
||||||
interface StreamInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Reads all data from the stream into a string, from the beginning to end.
|
|
||||||
*
|
|
||||||
* This method MUST attempt to seek to the beginning of the stream before
|
|
||||||
* reading data and read the stream until the end is reached.
|
|
||||||
*
|
|
||||||
* Warning: This could attempt to load a large amount of data into memory.
|
|
||||||
*
|
|
||||||
* This method MUST NOT raise an exception in order to conform with PHP's
|
|
||||||
* string casting operations.
|
|
||||||
*
|
|
||||||
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function __toString();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the stream and any underlying resources.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function close();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Separates any underlying resources from the stream.
|
|
||||||
*
|
|
||||||
* After the stream has been detached, the stream is in an unusable state.
|
|
||||||
*
|
|
||||||
* @return resource|null Underlying PHP stream, if any
|
|
||||||
*/
|
|
||||||
public function detach();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the size of the stream if known.
|
|
||||||
*
|
|
||||||
* @return int|null Returns the size in bytes if known, or null if unknown.
|
|
||||||
*/
|
|
||||||
public function getSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current position of the file read/write pointer
|
|
||||||
*
|
|
||||||
* @return int Position of the file pointer
|
|
||||||
* @throws \RuntimeException on error.
|
|
||||||
*/
|
|
||||||
public function tell();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the stream is at the end of the stream.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function eof();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the stream is seekable.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isSeekable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seek to a position in the stream.
|
|
||||||
*
|
|
||||||
* @link http://www.php.net/manual/en/function.fseek.php
|
|
||||||
* @param int $offset Stream offset
|
|
||||||
* @param int $whence Specifies how the cursor position will be calculated
|
|
||||||
* based on the seek offset. Valid values are identical to the built-in
|
|
||||||
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
|
|
||||||
* offset bytes SEEK_CUR: Set position to current location plus offset
|
|
||||||
* SEEK_END: Set position to end-of-stream plus offset.
|
|
||||||
* @throws \RuntimeException on failure.
|
|
||||||
*/
|
|
||||||
public function seek(int $offset, int $whence = SEEK_SET);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seek to the beginning of the stream.
|
|
||||||
*
|
|
||||||
* If the stream is not seekable, this method will raise an exception;
|
|
||||||
* otherwise, it will perform a seek(0).
|
|
||||||
*
|
|
||||||
* @see seek()
|
|
||||||
* @link http://www.php.net/manual/en/function.fseek.php
|
|
||||||
* @throws \RuntimeException on failure.
|
|
||||||
*/
|
|
||||||
public function rewind();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the stream is writable.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isWritable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write data to the stream.
|
|
||||||
*
|
|
||||||
* @param string $string The string that is to be written.
|
|
||||||
* @return int Returns the number of bytes written to the stream.
|
|
||||||
* @throws \RuntimeException on failure.
|
|
||||||
*/
|
|
||||||
public function write(string $string);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the stream is readable.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isReadable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read data from the stream.
|
|
||||||
*
|
|
||||||
* @param int $length Read up to $length bytes from the object and return
|
|
||||||
* them. Fewer than $length bytes may be returned if underlying stream
|
|
||||||
* call returns fewer bytes.
|
|
||||||
* @return string Returns the data read from the stream, or an empty string
|
|
||||||
* if no bytes are available.
|
|
||||||
* @throws \RuntimeException if an error occurs.
|
|
||||||
*/
|
|
||||||
public function read(int $length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the remaining contents in a string
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \RuntimeException if unable to read or an error occurs while
|
|
||||||
* reading.
|
|
||||||
*/
|
|
||||||
public function getContents();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get stream metadata as an associative array or retrieve a specific key.
|
|
||||||
*
|
|
||||||
* The keys returned are identical to the keys returned from PHP's
|
|
||||||
* stream_get_meta_data() function.
|
|
||||||
*
|
|
||||||
* @link http://php.net/manual/en/function.stream-get-meta-data.php
|
|
||||||
* @param string|null $key Specific metadata to retrieve.
|
|
||||||
* @return array|mixed|null Returns an associative array if no key is
|
|
||||||
* provided. Returns a specific key value if a key is provided and the
|
|
||||||
* value is found, or null if the key is not found.
|
|
||||||
*/
|
|
||||||
public function getMetadata(?string $key = null);
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Psr\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value object representing a file uploaded through an HTTP request.
|
|
||||||
*
|
|
||||||
* Instances of this interface are considered immutable; all methods that
|
|
||||||
* might change state MUST be implemented such that they retain the internal
|
|
||||||
* state of the current instance and return an instance that contains the
|
|
||||||
* changed state.
|
|
||||||
*/
|
|
||||||
interface UploadedFileInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Retrieve a stream representing the uploaded file.
|
|
||||||
*
|
|
||||||
* This method MUST return a StreamInterface instance, representing the
|
|
||||||
* uploaded file. The purpose of this method is to allow utilizing native PHP
|
|
||||||
* stream functionality to manipulate the file upload, such as
|
|
||||||
* stream_copy_to_stream() (though the result will need to be decorated in a
|
|
||||||
* native PHP stream wrapper to work with such functions).
|
|
||||||
*
|
|
||||||
* If the moveTo() method has been called previously, this method MUST raise
|
|
||||||
* an exception.
|
|
||||||
*
|
|
||||||
* @return StreamInterface Stream representation of the uploaded file.
|
|
||||||
* @throws \RuntimeException in cases when no stream is available or can be
|
|
||||||
* created.
|
|
||||||
*/
|
|
||||||
public function getStream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move the uploaded file to a new location.
|
|
||||||
*
|
|
||||||
* Use this method as an alternative to move_uploaded_file(). This method is
|
|
||||||
* guaranteed to work in both SAPI and non-SAPI environments.
|
|
||||||
* Implementations must determine which environment they are in, and use the
|
|
||||||
* appropriate method (move_uploaded_file(), rename(), or a stream
|
|
||||||
* operation) to perform the operation.
|
|
||||||
*
|
|
||||||
* $targetPath may be an absolute path, or a relative path. If it is a
|
|
||||||
* relative path, resolution should be the same as used by PHP's rename()
|
|
||||||
* function.
|
|
||||||
*
|
|
||||||
* The original file or stream MUST be removed on completion.
|
|
||||||
*
|
|
||||||
* If this method is called more than once, any subsequent calls MUST raise
|
|
||||||
* an exception.
|
|
||||||
*
|
|
||||||
* When used in an SAPI environment where $_FILES is populated, when writing
|
|
||||||
* files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
|
|
||||||
* used to ensure permissions and upload status are verified correctly.
|
|
||||||
*
|
|
||||||
* If you wish to move to a stream, use getStream(), as SAPI operations
|
|
||||||
* cannot guarantee writing to stream destinations.
|
|
||||||
*
|
|
||||||
* @see http://php.net/is_uploaded_file
|
|
||||||
* @see http://php.net/move_uploaded_file
|
|
||||||
* @param string $targetPath Path to which to move the uploaded file.
|
|
||||||
* @throws \InvalidArgumentException if the $targetPath specified is invalid.
|
|
||||||
* @throws \RuntimeException on any error during the move operation, or on
|
|
||||||
* the second or subsequent call to the method.
|
|
||||||
*/
|
|
||||||
public function moveTo(string $targetPath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the file size.
|
|
||||||
*
|
|
||||||
* Implementations SHOULD return the value stored in the "size" key of
|
|
||||||
* the file in the $_FILES array if available, as PHP calculates this based
|
|
||||||
* on the actual size transmitted.
|
|
||||||
*
|
|
||||||
* @return int|null The file size in bytes or null if unknown.
|
|
||||||
*/
|
|
||||||
public function getSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the error associated with the uploaded file.
|
|
||||||
*
|
|
||||||
* The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
|
|
||||||
*
|
|
||||||
* If the file was uploaded successfully, this method MUST return
|
|
||||||
* UPLOAD_ERR_OK.
|
|
||||||
*
|
|
||||||
* Implementations SHOULD return the value stored in the "error" key of
|
|
||||||
* the file in the $_FILES array.
|
|
||||||
*
|
|
||||||
* @see http://php.net/manual/en/features.file-upload.errors.php
|
|
||||||
* @return int One of PHP's UPLOAD_ERR_XXX constants.
|
|
||||||
*/
|
|
||||||
public function getError();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the filename sent by the client.
|
|
||||||
*
|
|
||||||
* Do not trust the value returned by this method. A client could send
|
|
||||||
* a malicious filename with the intention to corrupt or hack your
|
|
||||||
* application.
|
|
||||||
*
|
|
||||||
* Implementations SHOULD return the value stored in the "name" key of
|
|
||||||
* the file in the $_FILES array.
|
|
||||||
*
|
|
||||||
* @return string|null The filename sent by the client or null if none
|
|
||||||
* was provided.
|
|
||||||
*/
|
|
||||||
public function getClientFilename();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the media type sent by the client.
|
|
||||||
*
|
|
||||||
* Do not trust the value returned by this method. A client could send
|
|
||||||
* a malicious media type with the intention to corrupt or hack your
|
|
||||||
* application.
|
|
||||||
*
|
|
||||||
* Implementations SHOULD return the value stored in the "type" key of
|
|
||||||
* the file in the $_FILES array.
|
|
||||||
*
|
|
||||||
* @return string|null The media type sent by the client or null if none
|
|
||||||
* was provided.
|
|
||||||
*/
|
|
||||||
public function getClientMediaType();
|
|
||||||
}
|
|
||||||
326
vendor/psr/http-message/src/UriInterface.php
vendored
326
vendor/psr/http-message/src/UriInterface.php
vendored
@@ -1,326 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Psr\Http\Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value object representing a URI.
|
|
||||||
*
|
|
||||||
* This interface is meant to represent URIs according to RFC 3986 and to
|
|
||||||
* provide methods for most common operations. Additional functionality for
|
|
||||||
* working with URIs can be provided on top of the interface or externally.
|
|
||||||
* Its primary use is for HTTP requests, but may also be used in other
|
|
||||||
* contexts.
|
|
||||||
*
|
|
||||||
* Instances of this interface are considered immutable; all methods that
|
|
||||||
* might change state MUST be implemented such that they retain the internal
|
|
||||||
* state of the current instance and return an instance that contains the
|
|
||||||
* changed state.
|
|
||||||
*
|
|
||||||
* Typically the Host header will be also be present in the request message.
|
|
||||||
* For server-side requests, the scheme will typically be discoverable in the
|
|
||||||
* server parameters.
|
|
||||||
*
|
|
||||||
* @link http://tools.ietf.org/html/rfc3986 (the URI specification)
|
|
||||||
*/
|
|
||||||
interface UriInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Retrieve the scheme component of the URI.
|
|
||||||
*
|
|
||||||
* If no scheme is present, this method MUST return an empty string.
|
|
||||||
*
|
|
||||||
* The value returned MUST be normalized to lowercase, per RFC 3986
|
|
||||||
* Section 3.1.
|
|
||||||
*
|
|
||||||
* The trailing ":" character is not part of the scheme and MUST NOT be
|
|
||||||
* added.
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-3.1
|
|
||||||
* @return string The URI scheme.
|
|
||||||
*/
|
|
||||||
public function getScheme();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the authority component of the URI.
|
|
||||||
*
|
|
||||||
* If no authority information is present, this method MUST return an empty
|
|
||||||
* string.
|
|
||||||
*
|
|
||||||
* The authority syntax of the URI is:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* [user-info@]host[:port]
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* If the port component is not set or is the standard port for the current
|
|
||||||
* scheme, it SHOULD NOT be included.
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-3.2
|
|
||||||
* @return string The URI authority, in "[user-info@]host[:port]" format.
|
|
||||||
*/
|
|
||||||
public function getAuthority();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the user information component of the URI.
|
|
||||||
*
|
|
||||||
* If no user information is present, this method MUST return an empty
|
|
||||||
* string.
|
|
||||||
*
|
|
||||||
* If a user is present in the URI, this will return that value;
|
|
||||||
* additionally, if the password is also present, it will be appended to the
|
|
||||||
* user value, with a colon (":") separating the values.
|
|
||||||
*
|
|
||||||
* The trailing "@" character is not part of the user information and MUST
|
|
||||||
* NOT be added.
|
|
||||||
*
|
|
||||||
* @return string The URI user information, in "username[:password]" format.
|
|
||||||
*/
|
|
||||||
public function getUserInfo();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the host component of the URI.
|
|
||||||
*
|
|
||||||
* If no host is present, this method MUST return an empty string.
|
|
||||||
*
|
|
||||||
* The value returned MUST be normalized to lowercase, per RFC 3986
|
|
||||||
* Section 3.2.2.
|
|
||||||
*
|
|
||||||
* @see http://tools.ietf.org/html/rfc3986#section-3.2.2
|
|
||||||
* @return string The URI host.
|
|
||||||
*/
|
|
||||||
public function getHost();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the port component of the URI.
|
|
||||||
*
|
|
||||||
* If a port is present, and it is non-standard for the current scheme,
|
|
||||||
* this method MUST return it as an integer. If the port is the standard port
|
|
||||||
* used with the current scheme, this method SHOULD return null.
|
|
||||||
*
|
|
||||||
* If no port is present, and no scheme is present, this method MUST return
|
|
||||||
* a null value.
|
|
||||||
*
|
|
||||||
* If no port is present, but a scheme is present, this method MAY return
|
|
||||||
* the standard port for that scheme, but SHOULD return null.
|
|
||||||
*
|
|
||||||
* @return null|int The URI port.
|
|
||||||
*/
|
|
||||||
public function getPort();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the path component of the URI.
|
|
||||||
*
|
|
||||||
* The path can either be empty or absolute (starting with a slash) or
|
|
||||||
* rootless (not starting with a slash). Implementations MUST support all
|
|
||||||
* three syntaxes.
|
|
||||||
*
|
|
||||||
* Normally, the empty path "" and absolute path "/" are considered equal as
|
|
||||||
* defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
|
|
||||||
* do this normalization because in contexts with a trimmed base path, e.g.
|
|
||||||
* the front controller, this difference becomes significant. It's the task
|
|
||||||
* of the user to handle both "" and "/".
|
|
||||||
*
|
|
||||||
* The value returned MUST be percent-encoded, but MUST NOT double-encode
|
|
||||||
* any characters. To determine what characters to encode, please refer to
|
|
||||||
* RFC 3986, Sections 2 and 3.3.
|
|
||||||
*
|
|
||||||
* As an example, if the value should include a slash ("/") not intended as
|
|
||||||
* delimiter between path segments, that value MUST be passed in encoded
|
|
||||||
* form (e.g., "%2F") to the instance.
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-2
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-3.3
|
|
||||||
* @return string The URI path.
|
|
||||||
*/
|
|
||||||
public function getPath();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the query string of the URI.
|
|
||||||
*
|
|
||||||
* If no query string is present, this method MUST return an empty string.
|
|
||||||
*
|
|
||||||
* The leading "?" character is not part of the query and MUST NOT be
|
|
||||||
* added.
|
|
||||||
*
|
|
||||||
* The value returned MUST be percent-encoded, but MUST NOT double-encode
|
|
||||||
* any characters. To determine what characters to encode, please refer to
|
|
||||||
* RFC 3986, Sections 2 and 3.4.
|
|
||||||
*
|
|
||||||
* As an example, if a value in a key/value pair of the query string should
|
|
||||||
* include an ampersand ("&") not intended as a delimiter between values,
|
|
||||||
* that value MUST be passed in encoded form (e.g., "%26") to the instance.
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-2
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-3.4
|
|
||||||
* @return string The URI query string.
|
|
||||||
*/
|
|
||||||
public function getQuery();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the fragment component of the URI.
|
|
||||||
*
|
|
||||||
* If no fragment is present, this method MUST return an empty string.
|
|
||||||
*
|
|
||||||
* The leading "#" character is not part of the fragment and MUST NOT be
|
|
||||||
* added.
|
|
||||||
*
|
|
||||||
* The value returned MUST be percent-encoded, but MUST NOT double-encode
|
|
||||||
* any characters. To determine what characters to encode, please refer to
|
|
||||||
* RFC 3986, Sections 2 and 3.5.
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-2
|
|
||||||
* @see https://tools.ietf.org/html/rfc3986#section-3.5
|
|
||||||
* @return string The URI fragment.
|
|
||||||
*/
|
|
||||||
public function getFragment();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified scheme.
|
|
||||||
*
|
|
||||||
* This method MUST retain the state of the current instance, and return
|
|
||||||
* an instance that contains the specified scheme.
|
|
||||||
*
|
|
||||||
* Implementations MUST support the schemes "http" and "https" case
|
|
||||||
* insensitively, and MAY accommodate other schemes if required.
|
|
||||||
*
|
|
||||||
* An empty scheme is equivalent to removing the scheme.
|
|
||||||
*
|
|
||||||
* @param string $scheme The scheme to use with the new instance.
|
|
||||||
* @return static A new instance with the specified scheme.
|
|
||||||
* @throws \InvalidArgumentException for invalid or unsupported schemes.
|
|
||||||
*/
|
|
||||||
public function withScheme(string $scheme);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified user information.
|
|
||||||
*
|
|
||||||
* This method MUST retain the state of the current instance, and return
|
|
||||||
* an instance that contains the specified user information.
|
|
||||||
*
|
|
||||||
* Password is optional, but the user information MUST include the
|
|
||||||
* user; an empty string for the user is equivalent to removing user
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* @param string $user The user name to use for authority.
|
|
||||||
* @param null|string $password The password associated with $user.
|
|
||||||
* @return static A new instance with the specified user information.
|
|
||||||
*/
|
|
||||||
public function withUserInfo(string $user, ?string $password = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified host.
|
|
||||||
*
|
|
||||||
* This method MUST retain the state of the current instance, and return
|
|
||||||
* an instance that contains the specified host.
|
|
||||||
*
|
|
||||||
* An empty host value is equivalent to removing the host.
|
|
||||||
*
|
|
||||||
* @param string $host The hostname to use with the new instance.
|
|
||||||
* @return static A new instance with the specified host.
|
|
||||||
* @throws \InvalidArgumentException for invalid hostnames.
|
|
||||||
*/
|
|
||||||
public function withHost(string $host);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified port.
|
|
||||||
*
|
|
||||||
* This method MUST retain the state of the current instance, and return
|
|
||||||
* an instance that contains the specified port.
|
|
||||||
*
|
|
||||||
* Implementations MUST raise an exception for ports outside the
|
|
||||||
* established TCP and UDP port ranges.
|
|
||||||
*
|
|
||||||
* A null value provided for the port is equivalent to removing the port
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* @param null|int $port The port to use with the new instance; a null value
|
|
||||||
* removes the port information.
|
|
||||||
* @return static A new instance with the specified port.
|
|
||||||
* @throws \InvalidArgumentException for invalid ports.
|
|
||||||
*/
|
|
||||||
public function withPort(?int $port);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified path.
|
|
||||||
*
|
|
||||||
* This method MUST retain the state of the current instance, and return
|
|
||||||
* an instance that contains the specified path.
|
|
||||||
*
|
|
||||||
* The path can either be empty or absolute (starting with a slash) or
|
|
||||||
* rootless (not starting with a slash). Implementations MUST support all
|
|
||||||
* three syntaxes.
|
|
||||||
*
|
|
||||||
* If the path is intended to be domain-relative rather than path relative then
|
|
||||||
* it must begin with a slash ("/"). Paths not starting with a slash ("/")
|
|
||||||
* are assumed to be relative to some base path known to the application or
|
|
||||||
* consumer.
|
|
||||||
*
|
|
||||||
* Users can provide both encoded and decoded path characters.
|
|
||||||
* Implementations ensure the correct encoding as outlined in getPath().
|
|
||||||
*
|
|
||||||
* @param string $path The path to use with the new instance.
|
|
||||||
* @return static A new instance with the specified path.
|
|
||||||
* @throws \InvalidArgumentException for invalid paths.
|
|
||||||
*/
|
|
||||||
public function withPath(string $path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified query string.
|
|
||||||
*
|
|
||||||
* This method MUST retain the state of the current instance, and return
|
|
||||||
* an instance that contains the specified query string.
|
|
||||||
*
|
|
||||||
* Users can provide both encoded and decoded query characters.
|
|
||||||
* Implementations ensure the correct encoding as outlined in getQuery().
|
|
||||||
*
|
|
||||||
* An empty query string value is equivalent to removing the query string.
|
|
||||||
*
|
|
||||||
* @param string $query The query string to use with the new instance.
|
|
||||||
* @return static A new instance with the specified query string.
|
|
||||||
* @throws \InvalidArgumentException for invalid query strings.
|
|
||||||
*/
|
|
||||||
public function withQuery(string $query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance with the specified URI fragment.
|
|
||||||
*
|
|
||||||
* This method MUST retain the state of the current instance, and return
|
|
||||||
* an instance that contains the specified URI fragment.
|
|
||||||
*
|
|
||||||
* Users can provide both encoded and decoded fragment characters.
|
|
||||||
* Implementations ensure the correct encoding as outlined in getFragment().
|
|
||||||
*
|
|
||||||
* An empty fragment value is equivalent to removing the fragment.
|
|
||||||
*
|
|
||||||
* @param string $fragment The fragment to use with the new instance.
|
|
||||||
* @return static A new instance with the specified fragment.
|
|
||||||
*/
|
|
||||||
public function withFragment(string $fragment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the string representation as a URI reference.
|
|
||||||
*
|
|
||||||
* Depending on which components of the URI are present, the resulting
|
|
||||||
* string is either a full URI or relative reference according to RFC 3986,
|
|
||||||
* Section 4.1. The method concatenates the various components of the URI,
|
|
||||||
* using the appropriate delimiters:
|
|
||||||
*
|
|
||||||
* - If a scheme is present, it MUST be suffixed by ":".
|
|
||||||
* - If an authority is present, it MUST be prefixed by "//".
|
|
||||||
* - The path can be concatenated without delimiters. But there are two
|
|
||||||
* cases where the path has to be adjusted to make the URI reference
|
|
||||||
* valid as PHP does not allow to throw an exception in __toString():
|
|
||||||
* - If the path is rootless and an authority is present, the path MUST
|
|
||||||
* be prefixed by "/".
|
|
||||||
* - If the path is starting with more than one "/" and no authority is
|
|
||||||
* present, the starting slashes MUST be reduced to one.
|
|
||||||
* - If a query is present, it MUST be prefixed by "?".
|
|
||||||
* - If a fragment is present, it MUST be prefixed by "#".
|
|
||||||
*
|
|
||||||
* @see http://tools.ietf.org/html/rfc3986#section-4.1
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function __toString();
|
|
||||||
}
|
|
||||||
112
vendor/react/async/CHANGELOG.md
vendored
112
vendor/react/async/CHANGELOG.md
vendored
@@ -1,112 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## 4.3.0 (2024-06-04)
|
|
||||||
|
|
||||||
* Feature: Improve performance by avoiding unneeded references in `FiberMap`.
|
|
||||||
(#88 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
|
||||||
(#87 by @clue)
|
|
||||||
|
|
||||||
* Improve type safety for test environment.
|
|
||||||
(#86 by @SimonFrings)
|
|
||||||
|
|
||||||
## 4.2.0 (2023-11-22)
|
|
||||||
|
|
||||||
* Feature: Add Promise v3 template types for all public functions.
|
|
||||||
(#40 by @WyriHaximus and @clue)
|
|
||||||
|
|
||||||
All our public APIs now use Promise v3 template types to guide IDEs and static
|
|
||||||
analysis tools (like PHPStan), helping with proper type usage and improving
|
|
||||||
code quality:
|
|
||||||
|
|
||||||
```php
|
|
||||||
assertType('bool', await(resolve(true)));
|
|
||||||
assertType('PromiseInterface<bool>', async(fn(): bool => true)());
|
|
||||||
assertType('PromiseInterface<bool>', coroutine(fn(): bool => true));
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Full PHP 8.3 compatibility.
|
|
||||||
(#81 by @clue)
|
|
||||||
|
|
||||||
* Update test suite to avoid unhandled promise rejections.
|
|
||||||
(#79 by @clue)
|
|
||||||
|
|
||||||
## 4.1.0 (2023-06-22)
|
|
||||||
|
|
||||||
* Feature: Add new `delay()` function to delay program execution.
|
|
||||||
(#69 and #78 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
echo 'a';
|
|
||||||
Loop::addTimer(1.0, function () {
|
|
||||||
echo 'b';
|
|
||||||
});
|
|
||||||
React\Async\delay(3.0);
|
|
||||||
echo 'c';
|
|
||||||
|
|
||||||
// prints "a" at t=0.0s
|
|
||||||
// prints "b" at t=1.0s
|
|
||||||
// prints "c" at t=3.0s
|
|
||||||
```
|
|
||||||
|
|
||||||
* Update test suite, add PHPStan with `max` level and report failed assertions.
|
|
||||||
(#66 and #76 by @clue and #61 and #73 by @WyriHaximus)
|
|
||||||
|
|
||||||
## 4.0.0 (2022-07-11)
|
|
||||||
|
|
||||||
A major new feature release, see [**release announcement**](https://clue.engineering/2022/announcing-reactphp-async).
|
|
||||||
|
|
||||||
* We'd like to emphasize that this component is production ready and battle-tested.
|
|
||||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
|
||||||
so you have a rock-solid foundation to build on top of.
|
|
||||||
|
|
||||||
* The v4 release will be the way forward for this package. However, we will still
|
|
||||||
actively support v3 and v2 to provide a smooth upgrade path for those not yet
|
|
||||||
on PHP 8.1+. If you're using an older PHP version, you may use either version
|
|
||||||
which all provide a compatible API but may not take advantage of newer language
|
|
||||||
features. You may target multiple versions at the same time to support a wider range of
|
|
||||||
PHP versions:
|
|
||||||
|
|
||||||
* [`4.x` branch](https://github.com/reactphp/async/tree/4.x) (PHP 8.1+)
|
|
||||||
* [`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+)
|
|
||||||
* [`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+)
|
|
||||||
|
|
||||||
This update involves some major new features and a minor BC break over the
|
|
||||||
`v3.0.0` release. We've tried hard to avoid BC breaks where possible and
|
|
||||||
minimize impact otherwise. We expect that most consumers of this package will be
|
|
||||||
affected by BC breaks, but updating should take no longer than a few minutes.
|
|
||||||
See below for more details:
|
|
||||||
|
|
||||||
* Feature / BC break: Require PHP 8.1+ and add `mixed` type declarations.
|
|
||||||
(#14 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add Fiber-based `async()` and `await()` functions.
|
|
||||||
(#15, #18, #19 and #20 by @WyriHaximus and #26, #28, #30, #32, #34, #55 and #57 by @clue)
|
|
||||||
|
|
||||||
* Project maintenance, rename `main` branch to `4.x` and update installation instructions.
|
|
||||||
(#29 by @clue)
|
|
||||||
|
|
||||||
The following changes had to be ported to this release due to our branching
|
|
||||||
strategy, but also appeared in the `v3.0.0` release:
|
|
||||||
|
|
||||||
* Feature: Support iterable type for `parallel()` + `series()` + `waterfall()`.
|
|
||||||
(#49 by @clue)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with upcoming Promise v3.
|
|
||||||
(#48 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#36 by @SimonFrings and #51 by @nhedger)
|
|
||||||
|
|
||||||
## 3.0.0 (2022-07-11)
|
|
||||||
|
|
||||||
See [`3.x` CHANGELOG](https://github.com/reactphp/async/blob/3.x/CHANGELOG.md) for more details.
|
|
||||||
|
|
||||||
## 2.0.0 (2022-07-11)
|
|
||||||
|
|
||||||
See [`2.x` CHANGELOG](https://github.com/reactphp/async/blob/2.x/CHANGELOG.md) for more details.
|
|
||||||
|
|
||||||
## 1.0.0 (2013-02-07)
|
|
||||||
|
|
||||||
* First tagged release
|
|
||||||
19
vendor/react/async/LICENSE
vendored
19
vendor/react/async/LICENSE
vendored
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
|
||||||
to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
672
vendor/react/async/README.md
vendored
672
vendor/react/async/README.md
vendored
@@ -1,672 +0,0 @@
|
|||||||
# Async Utilities
|
|
||||||
|
|
||||||
[](https://github.com/reactphp/async/actions)
|
|
||||||
[](https://packagist.org/packages/react/async)
|
|
||||||
|
|
||||||
Async utilities and fibers for [ReactPHP](https://reactphp.org/).
|
|
||||||
|
|
||||||
This library allows you to manage async control flow. It provides a number of
|
|
||||||
combinators for [Promise](https://github.com/reactphp/promise)-based APIs.
|
|
||||||
Instead of nesting or chaining promise callbacks, you can declare them as a
|
|
||||||
list, which is resolved sequentially in an async manner.
|
|
||||||
React/Async will not automagically change blocking code to be async. You need
|
|
||||||
to have an actual event loop and non-blocking libraries interacting with that
|
|
||||||
event loop for it to work. As long as you have a Promise-based API that runs in
|
|
||||||
an event loop, it can be used with this library.
|
|
||||||
|
|
||||||
**Table of Contents**
|
|
||||||
|
|
||||||
* [Usage](#usage)
|
|
||||||
* [async()](#async)
|
|
||||||
* [await()](#await)
|
|
||||||
* [coroutine()](#coroutine)
|
|
||||||
* [delay()](#delay)
|
|
||||||
* [parallel()](#parallel)
|
|
||||||
* [series()](#series)
|
|
||||||
* [waterfall()](#waterfall)
|
|
||||||
* [Todo](#todo)
|
|
||||||
* [Install](#install)
|
|
||||||
* [Tests](#tests)
|
|
||||||
* [License](#license)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
This lightweight library consists only of a few simple functions.
|
|
||||||
All functions reside under the `React\Async` namespace.
|
|
||||||
|
|
||||||
The below examples refer to all functions with their fully-qualified names like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
React\Async\await(…);
|
|
||||||
```
|
|
||||||
|
|
||||||
As of PHP 5.6+ you can also import each required function into your code like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
use function React\Async\await;
|
|
||||||
|
|
||||||
await(…);
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can also use an import statement similar to this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
use React\Async;
|
|
||||||
|
|
||||||
Async\await(…);
|
|
||||||
```
|
|
||||||
|
|
||||||
### async()
|
|
||||||
|
|
||||||
The `async(callable():(PromiseInterface<T>|T) $function): (callable():PromiseInterface<T>)` function can be used to
|
|
||||||
return an async function for a function that uses [`await()`](#await) internally.
|
|
||||||
|
|
||||||
This function is specifically designed to complement the [`await()` function](#await).
|
|
||||||
The [`await()` function](#await) can be considered *blocking* from the
|
|
||||||
perspective of the calling code. You can avoid this blocking behavior by
|
|
||||||
wrapping it in an `async()` function call. Everything inside this function
|
|
||||||
will still be blocked, but everything outside this function can be executed
|
|
||||||
asynchronously without blocking:
|
|
||||||
|
|
||||||
```php
|
|
||||||
Loop::addTimer(0.5, React\Async\async(function () {
|
|
||||||
echo 'a';
|
|
||||||
React\Async\await(React\Promise\Timer\sleep(1.0));
|
|
||||||
echo 'c';
|
|
||||||
}));
|
|
||||||
|
|
||||||
Loop::addTimer(1.0, function () {
|
|
||||||
echo 'b';
|
|
||||||
});
|
|
||||||
|
|
||||||
// prints "a" at t=0.5s
|
|
||||||
// prints "b" at t=1.0s
|
|
||||||
// prints "c" at t=1.5s
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [`await()` function](#await) for more details.
|
|
||||||
|
|
||||||
Note that this function only works in tandem with the [`await()` function](#await).
|
|
||||||
In particular, this function does not "magically" make any blocking function
|
|
||||||
non-blocking:
|
|
||||||
|
|
||||||
```php
|
|
||||||
Loop::addTimer(0.5, React\Async\async(function () {
|
|
||||||
echo 'a';
|
|
||||||
sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
|
|
||||||
echo 'c';
|
|
||||||
}));
|
|
||||||
|
|
||||||
Loop::addTimer(1.0, function () {
|
|
||||||
echo 'b';
|
|
||||||
});
|
|
||||||
|
|
||||||
// prints "a" at t=0.5s
|
|
||||||
// prints "c" at t=1.5s: Correct timing, but wrong order
|
|
||||||
// prints "b" at t=1.5s: Triggered too late because it was blocked
|
|
||||||
```
|
|
||||||
|
|
||||||
As an alternative, you should always make sure to use this function in tandem
|
|
||||||
with the [`await()` function](#await) and an async API returning a promise
|
|
||||||
as shown in the previous example.
|
|
||||||
|
|
||||||
The `async()` function is specifically designed for cases where it is used
|
|
||||||
as a callback (such as an event loop timer, event listener, or promise
|
|
||||||
callback). For this reason, it returns a new function wrapping the given
|
|
||||||
`$function` instead of directly invoking it and returning its value.
|
|
||||||
|
|
||||||
```php
|
|
||||||
use function React\Async\async;
|
|
||||||
|
|
||||||
Loop::addTimer(1.0, async(function () { … }));
|
|
||||||
$connection->on('close', async(function () { … }));
|
|
||||||
$stream->on('data', async(function ($data) { … }));
|
|
||||||
$promise->then(async(function (int $result) { … }));
|
|
||||||
```
|
|
||||||
|
|
||||||
You can invoke this wrapping function to invoke the given `$function` with
|
|
||||||
any arguments given as-is. The function will always return a Promise which
|
|
||||||
will be fulfilled with whatever your `$function` returns. Likewise, it will
|
|
||||||
return a promise that will be rejected if you throw an `Exception` or
|
|
||||||
`Throwable` from your `$function`. This allows you to easily create
|
|
||||||
Promise-based functions:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = React\Async\async(function (): int {
|
|
||||||
$browser = new React\Http\Browser();
|
|
||||||
$urls = [
|
|
||||||
'https://example.com/alice',
|
|
||||||
'https://example.com/bob'
|
|
||||||
];
|
|
||||||
|
|
||||||
$bytes = 0;
|
|
||||||
foreach ($urls as $url) {
|
|
||||||
$response = React\Async\await($browser->get($url));
|
|
||||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
$bytes += $response->getBody()->getSize();
|
|
||||||
}
|
|
||||||
return $bytes;
|
|
||||||
})();
|
|
||||||
|
|
||||||
$promise->then(function (int $bytes) {
|
|
||||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The previous example uses [`await()`](#await) inside a loop to highlight how
|
|
||||||
this vastly simplifies consuming asynchronous operations. At the same time,
|
|
||||||
this naive example does not leverage concurrent execution, as it will
|
|
||||||
essentially "await" between each operation. In order to take advantage of
|
|
||||||
concurrent execution within the given `$function`, you can "await" multiple
|
|
||||||
promises by using a single [`await()`](#await) together with Promise-based
|
|
||||||
primitives like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = React\Async\async(function (): int {
|
|
||||||
$browser = new React\Http\Browser();
|
|
||||||
$urls = [
|
|
||||||
'https://example.com/alice',
|
|
||||||
'https://example.com/bob'
|
|
||||||
];
|
|
||||||
|
|
||||||
$promises = [];
|
|
||||||
foreach ($urls as $url) {
|
|
||||||
$promises[] = $browser->get($url);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$responses = React\Async\await(React\Promise\all($promises));
|
|
||||||
} catch (Exception $e) {
|
|
||||||
foreach ($promises as $promise) {
|
|
||||||
$promise->cancel();
|
|
||||||
}
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
$bytes = 0;
|
|
||||||
foreach ($responses as $response) {
|
|
||||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
$bytes += $response->getBody()->getSize();
|
|
||||||
}
|
|
||||||
return $bytes;
|
|
||||||
})();
|
|
||||||
|
|
||||||
$promise->then(function (int $bytes) {
|
|
||||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The returned promise is implemented in such a way that it can be cancelled
|
|
||||||
when it is still pending. Cancelling a pending promise will cancel any awaited
|
|
||||||
promises inside that fiber or any nested fibers. As such, the following example
|
|
||||||
will only output `ab` and cancel the pending [`delay()`](#delay).
|
|
||||||
The [`await()`](#await) calls in this example would throw a `RuntimeException`
|
|
||||||
from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = async(static function (): int {
|
|
||||||
echo 'a';
|
|
||||||
await(async(static function (): void {
|
|
||||||
echo 'b';
|
|
||||||
delay(2);
|
|
||||||
echo 'c';
|
|
||||||
})());
|
|
||||||
echo 'd';
|
|
||||||
|
|
||||||
return time();
|
|
||||||
})();
|
|
||||||
|
|
||||||
$promise->cancel();
|
|
||||||
await($promise);
|
|
||||||
```
|
|
||||||
|
|
||||||
### await()
|
|
||||||
|
|
||||||
The `await(PromiseInterface<T> $promise): T` function can be used to
|
|
||||||
block waiting for the given `$promise` to be fulfilled.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$result = React\Async\await($promise);
|
|
||||||
```
|
|
||||||
|
|
||||||
This function will only return after the given `$promise` has settled, i.e.
|
|
||||||
either fulfilled or rejected. While the promise is pending, this function
|
|
||||||
can be considered *blocking* from the perspective of the calling code.
|
|
||||||
You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
|
|
||||||
call. Everything inside this function will still be blocked, but everything
|
|
||||||
outside this function can be executed asynchronously without blocking:
|
|
||||||
|
|
||||||
```php
|
|
||||||
Loop::addTimer(0.5, React\Async\async(function () {
|
|
||||||
echo 'a';
|
|
||||||
React\Async\await(React\Promise\Timer\sleep(1.0));
|
|
||||||
echo 'c';
|
|
||||||
}));
|
|
||||||
|
|
||||||
Loop::addTimer(1.0, function () {
|
|
||||||
echo 'b';
|
|
||||||
});
|
|
||||||
|
|
||||||
// prints "a" at t=0.5s
|
|
||||||
// prints "b" at t=1.0s
|
|
||||||
// prints "c" at t=1.5s
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [`async()` function](#async) for more details.
|
|
||||||
|
|
||||||
Once the promise is fulfilled, this function will return whatever the promise
|
|
||||||
resolved to.
|
|
||||||
|
|
||||||
Once the promise is rejected, this will throw whatever the promise rejected
|
|
||||||
with. If the promise did not reject with an `Exception` or `Throwable`, then
|
|
||||||
this function will throw an `UnexpectedValueException` instead.
|
|
||||||
|
|
||||||
```php
|
|
||||||
try {
|
|
||||||
$result = React\Async\await($promise);
|
|
||||||
// promise successfully fulfilled with $result
|
|
||||||
echo 'Result: ' . $result;
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
// promise rejected with $e
|
|
||||||
echo 'Error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### coroutine()
|
|
||||||
|
|
||||||
The `coroutine(callable(mixed ...$args):(\Generator|PromiseInterface<T>|T) $function, mixed ...$args): PromiseInterface<T>` function can be used to
|
|
||||||
execute a Generator-based coroutine to "await" promises.
|
|
||||||
|
|
||||||
```php
|
|
||||||
React\Async\coroutine(function () {
|
|
||||||
$browser = new React\Http\Browser();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = yield $browser->get('https://example.com/');
|
|
||||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
echo $response->getBody();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Using Generator-based coroutines is an alternative to directly using the
|
|
||||||
underlying promise APIs. For many use cases, this makes using promise-based
|
|
||||||
APIs much simpler, as it resembles a synchronous code flow more closely.
|
|
||||||
The above example performs the equivalent of directly using the promise APIs:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$browser = new React\Http\Browser();
|
|
||||||
|
|
||||||
$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
|
|
||||||
echo $response->getBody();
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The `yield` keyword can be used to "await" a promise resolution. Internally,
|
|
||||||
it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
|
|
||||||
This allows the execution to be interrupted and resumed at the same place
|
|
||||||
when the promise is fulfilled. The `yield` statement returns whatever the
|
|
||||||
promise is fulfilled with. If the promise is rejected, it will throw an
|
|
||||||
`Exception` or `Throwable`.
|
|
||||||
|
|
||||||
The `coroutine()` function will always return a Promise which will be
|
|
||||||
fulfilled with whatever your `$function` returns. Likewise, it will return
|
|
||||||
a promise that will be rejected if you throw an `Exception` or `Throwable`
|
|
||||||
from your `$function`. This allows you to easily create Promise-based
|
|
||||||
functions:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = React\Async\coroutine(function () {
|
|
||||||
$browser = new React\Http\Browser();
|
|
||||||
$urls = [
|
|
||||||
'https://example.com/alice',
|
|
||||||
'https://example.com/bob'
|
|
||||||
];
|
|
||||||
|
|
||||||
$bytes = 0;
|
|
||||||
foreach ($urls as $url) {
|
|
||||||
$response = yield $browser->get($url);
|
|
||||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
$bytes += $response->getBody()->getSize();
|
|
||||||
}
|
|
||||||
return $bytes;
|
|
||||||
});
|
|
||||||
|
|
||||||
$promise->then(function (int $bytes) {
|
|
||||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The previous example uses a `yield` statement inside a loop to highlight how
|
|
||||||
this vastly simplifies consuming asynchronous operations. At the same time,
|
|
||||||
this naive example does not leverage concurrent execution, as it will
|
|
||||||
essentially "await" between each operation. In order to take advantage of
|
|
||||||
concurrent execution within the given `$function`, you can "await" multiple
|
|
||||||
promises by using a single `yield` together with Promise-based primitives
|
|
||||||
like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = React\Async\coroutine(function () {
|
|
||||||
$browser = new React\Http\Browser();
|
|
||||||
$urls = [
|
|
||||||
'https://example.com/alice',
|
|
||||||
'https://example.com/bob'
|
|
||||||
];
|
|
||||||
|
|
||||||
$promises = [];
|
|
||||||
foreach ($urls as $url) {
|
|
||||||
$promises[] = $browser->get($url);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$responses = yield React\Promise\all($promises);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
foreach ($promises as $promise) {
|
|
||||||
$promise->cancel();
|
|
||||||
}
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
$bytes = 0;
|
|
||||||
foreach ($responses as $response) {
|
|
||||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
$bytes += $response->getBody()->getSize();
|
|
||||||
}
|
|
||||||
return $bytes;
|
|
||||||
});
|
|
||||||
|
|
||||||
$promise->then(function (int $bytes) {
|
|
||||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### delay()
|
|
||||||
|
|
||||||
The `delay(float $seconds): void` function can be used to
|
|
||||||
delay program execution for duration given in `$seconds`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
React\Async\delay($seconds);
|
|
||||||
```
|
|
||||||
|
|
||||||
This function will only return after the given number of `$seconds` have
|
|
||||||
elapsed. If there are no other events attached to this loop, it will behave
|
|
||||||
similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
|
|
||||||
|
|
||||||
```php
|
|
||||||
echo 'a';
|
|
||||||
React\Async\delay(1.0);
|
|
||||||
echo 'b';
|
|
||||||
|
|
||||||
// prints "a" at t=0.0s
|
|
||||||
// prints "b" at t=1.0s
|
|
||||||
```
|
|
||||||
|
|
||||||
Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
|
|
||||||
this function may not necessarily halt execution of the entire process thread.
|
|
||||||
Instead, it allows the event loop to run any other events attached to the
|
|
||||||
same loop until the delay returns:
|
|
||||||
|
|
||||||
```php
|
|
||||||
echo 'a';
|
|
||||||
Loop::addTimer(1.0, function (): void {
|
|
||||||
echo 'b';
|
|
||||||
});
|
|
||||||
React\Async\delay(3.0);
|
|
||||||
echo 'c';
|
|
||||||
|
|
||||||
// prints "a" at t=0.0s
|
|
||||||
// prints "b" at t=1.0s
|
|
||||||
// prints "c" at t=3.0s
|
|
||||||
```
|
|
||||||
|
|
||||||
This behavior is especially useful if you want to delay the program execution
|
|
||||||
of a particular routine, such as when building a simple polling or retry
|
|
||||||
mechanism:
|
|
||||||
|
|
||||||
```php
|
|
||||||
try {
|
|
||||||
something();
|
|
||||||
} catch (Throwable) {
|
|
||||||
// in case of error, retry after a short delay
|
|
||||||
React\Async\delay(1.0);
|
|
||||||
something();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Because this function only returns after some time has passed, it can be
|
|
||||||
considered *blocking* from the perspective of the calling code. You can avoid
|
|
||||||
this blocking behavior by wrapping it in an [`async()` function](#async) call.
|
|
||||||
Everything inside this function will still be blocked, but everything outside
|
|
||||||
this function can be executed asynchronously without blocking:
|
|
||||||
|
|
||||||
```php
|
|
||||||
Loop::addTimer(0.5, React\Async\async(function (): void {
|
|
||||||
echo 'a';
|
|
||||||
React\Async\delay(1.0);
|
|
||||||
echo 'c';
|
|
||||||
}));
|
|
||||||
|
|
||||||
Loop::addTimer(1.0, function (): void {
|
|
||||||
echo 'b';
|
|
||||||
});
|
|
||||||
|
|
||||||
// prints "a" at t=0.5s
|
|
||||||
// prints "b" at t=1.0s
|
|
||||||
// prints "c" at t=1.5s
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [`async()` function](#async) for more details.
|
|
||||||
|
|
||||||
Internally, the `$seconds` argument will be used as a timer for the loop so that
|
|
||||||
it keeps running until this timer triggers. This implies that if you pass a
|
|
||||||
really small (or negative) value, it will still start a timer and will thus
|
|
||||||
trigger at the earliest possible time in the future.
|
|
||||||
|
|
||||||
The function is implemented in such a way that it can be cancelled when it is
|
|
||||||
running inside an [`async()` function](#async). Cancelling the resulting
|
|
||||||
promise will clean up any pending timers and throw a `RuntimeException` from
|
|
||||||
the pending delay which in turn would reject the resulting promise.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = async(function (): void {
|
|
||||||
echo 'a';
|
|
||||||
delay(3.0);
|
|
||||||
echo 'b';
|
|
||||||
})();
|
|
||||||
|
|
||||||
Loop::addTimer(2.0, function () use ($promise): void {
|
|
||||||
$promise->cancel();
|
|
||||||
});
|
|
||||||
|
|
||||||
// prints "a" at t=0.0s
|
|
||||||
// rejects $promise at t=2.0
|
|
||||||
// never prints "b"
|
|
||||||
```
|
|
||||||
|
|
||||||
### parallel()
|
|
||||||
|
|
||||||
The `parallel(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
|
|
||||||
like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
use React\EventLoop\Loop;
|
|
||||||
use React\Promise\Promise;
|
|
||||||
|
|
||||||
React\Async\parallel([
|
|
||||||
function () {
|
|
||||||
return new Promise(function ($resolve) {
|
|
||||||
Loop::addTimer(1, function () use ($resolve) {
|
|
||||||
$resolve('Slept for a whole second');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
return new Promise(function ($resolve) {
|
|
||||||
Loop::addTimer(1, function () use ($resolve) {
|
|
||||||
$resolve('Slept for another whole second');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
return new Promise(function ($resolve) {
|
|
||||||
Loop::addTimer(1, function () use ($resolve) {
|
|
||||||
$resolve('Slept for yet another whole second');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
])->then(function (array $results) {
|
|
||||||
foreach ($results as $result) {
|
|
||||||
var_dump($result);
|
|
||||||
}
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### series()
|
|
||||||
|
|
||||||
The `series(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
|
|
||||||
like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
use React\EventLoop\Loop;
|
|
||||||
use React\Promise\Promise;
|
|
||||||
|
|
||||||
React\Async\series([
|
|
||||||
function () {
|
|
||||||
return new Promise(function ($resolve) {
|
|
||||||
Loop::addTimer(1, function () use ($resolve) {
|
|
||||||
$resolve('Slept for a whole second');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
return new Promise(function ($resolve) {
|
|
||||||
Loop::addTimer(1, function () use ($resolve) {
|
|
||||||
$resolve('Slept for another whole second');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
return new Promise(function ($resolve) {
|
|
||||||
Loop::addTimer(1, function () use ($resolve) {
|
|
||||||
$resolve('Slept for yet another whole second');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
])->then(function (array $results) {
|
|
||||||
foreach ($results as $result) {
|
|
||||||
var_dump($result);
|
|
||||||
}
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### waterfall()
|
|
||||||
|
|
||||||
The `waterfall(iterable<callable(mixed=):PromiseInterface<T>> $tasks): PromiseInterface<T>` function can be used
|
|
||||||
like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
use React\EventLoop\Loop;
|
|
||||||
use React\Promise\Promise;
|
|
||||||
|
|
||||||
$addOne = function ($prev = 0) {
|
|
||||||
return new Promise(function ($resolve) use ($prev) {
|
|
||||||
Loop::addTimer(1, function () use ($prev, $resolve) {
|
|
||||||
$resolve($prev + 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
React\Async\waterfall([
|
|
||||||
$addOne,
|
|
||||||
$addOne,
|
|
||||||
$addOne
|
|
||||||
])->then(function ($prev) {
|
|
||||||
echo "Final result is $prev\n";
|
|
||||||
}, function (Exception $e) {
|
|
||||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Todo
|
|
||||||
|
|
||||||
* Implement queue()
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
|
||||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
|
||||||
|
|
||||||
This project follows [SemVer](https://semver.org/).
|
|
||||||
This will install the latest supported version from this branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer require react/async:^4.3
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
|
||||||
|
|
||||||
This project aims to run on any platform and thus does not require any PHP
|
|
||||||
extensions and supports running on PHP 8.1+.
|
|
||||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
|
||||||
|
|
||||||
We're committed to providing long-term support (LTS) options and to provide a
|
|
||||||
smooth upgrade path. If you're using an older PHP version, you may use the
|
|
||||||
[`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+) or
|
|
||||||
[`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+) which both
|
|
||||||
provide a compatible API but do not take advantage of newer language features.
|
|
||||||
You may target multiple versions at the same time to support a wider range of
|
|
||||||
PHP versions like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer require "react/async:^4 || ^3 || ^2"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
To run the test suite, you first need to clone this repo and then install all
|
|
||||||
dependencies [through Composer](https://getcomposer.org/):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer install
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the test suite, go to the project root and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vendor/bin/phpunit
|
|
||||||
```
|
|
||||||
|
|
||||||
On top of this, we use PHPStan on max level to ensure type safety across the project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vendor/bin/phpstan
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT, see [LICENSE file](LICENSE).
|
|
||||||
|
|
||||||
This project is heavily influenced by [async.js](https://github.com/caolan/async).
|
|
||||||
50
vendor/react/async/composer.json
vendored
50
vendor/react/async/composer.json
vendored
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "react/async",
|
|
||||||
"description": "Async utilities and fibers for ReactPHP",
|
|
||||||
"keywords": ["async", "ReactPHP"],
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Christian Lück",
|
|
||||||
"homepage": "https://clue.engineering/",
|
|
||||||
"email": "christian@clue.engineering"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cees-Jan Kiewiet",
|
|
||||||
"homepage": "https://wyrihaximus.net/",
|
|
||||||
"email": "reactphp@ceesjankiewiet.nl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jan Sorgalla",
|
|
||||||
"homepage": "https://sorgalla.com/",
|
|
||||||
"email": "jsorgalla@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Chris Boden",
|
|
||||||
"homepage": "https://cboden.dev/",
|
|
||||||
"email": "cboden@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=8.1",
|
|
||||||
"react/event-loop": "^1.2",
|
|
||||||
"react/promise": "^3.2 || ^2.8 || ^1.2.1"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpstan/phpstan": "1.10.39",
|
|
||||||
"phpunit/phpunit": "^9.6"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"React\\Async\\": "src/"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"src/functions_include.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"autoload-dev": {
|
|
||||||
"psr-4": {
|
|
||||||
"React\\Tests\\Async\\": "tests/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
33
vendor/react/async/src/FiberFactory.php
vendored
33
vendor/react/async/src/FiberFactory.php
vendored
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Async;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This factory its only purpose is interoperability. Where with
|
|
||||||
* event loops one could simply wrap another event loop. But with fibers
|
|
||||||
* that has become impossible and as such we provide this factory and the
|
|
||||||
* FiberInterface.
|
|
||||||
*
|
|
||||||
* Usage is not documented and as such not supported and might chang without
|
|
||||||
* notice. Use at your own risk.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class FiberFactory
|
|
||||||
{
|
|
||||||
private static ?\Closure $factory = null;
|
|
||||||
|
|
||||||
public static function create(): FiberInterface
|
|
||||||
{
|
|
||||||
return (self::factory())();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function factory(?\Closure $factory = null): \Closure
|
|
||||||
{
|
|
||||||
if ($factory !== null) {
|
|
||||||
self::$factory = $factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$factory ?? static fn (): FiberInterface => new SimpleFiber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
vendor/react/async/src/FiberInterface.php
vendored
23
vendor/react/async/src/FiberInterface.php
vendored
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Async;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface its only purpose is interoperability. Where with
|
|
||||||
* event loops one could simply wrap another event loop. But with fibers
|
|
||||||
* that has become impossible and as such we provide this interface and the
|
|
||||||
* FiberFactory.
|
|
||||||
*
|
|
||||||
* Usage is not documented and as such not supported and might chang without
|
|
||||||
* notice. Use at your own risk.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
interface FiberInterface
|
|
||||||
{
|
|
||||||
public function resume(mixed $value): void;
|
|
||||||
|
|
||||||
public function throw(\Throwable $throwable): void;
|
|
||||||
|
|
||||||
public function suspend(): mixed;
|
|
||||||
}
|
|
||||||
42
vendor/react/async/src/FiberMap.php
vendored
42
vendor/react/async/src/FiberMap.php
vendored
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Async;
|
|
||||||
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @template T
|
|
||||||
*/
|
|
||||||
final class FiberMap
|
|
||||||
{
|
|
||||||
/** @var array<int,PromiseInterface<T>> */
|
|
||||||
private static array $map = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
|
|
||||||
* @param PromiseInterface<T> $promise
|
|
||||||
*/
|
|
||||||
public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void
|
|
||||||
{
|
|
||||||
self::$map[\spl_object_id($fiber)] = $promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
|
|
||||||
*/
|
|
||||||
public static function unsetPromise(\Fiber $fiber): void
|
|
||||||
{
|
|
||||||
unset(self::$map[\spl_object_id($fiber)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
|
|
||||||
* @return ?PromiseInterface<T>
|
|
||||||
*/
|
|
||||||
public static function getPromise(\Fiber $fiber): ?PromiseInterface
|
|
||||||
{
|
|
||||||
return self::$map[\spl_object_id($fiber)] ?? null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
79
vendor/react/async/src/SimpleFiber.php
vendored
79
vendor/react/async/src/SimpleFiber.php
vendored
@@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Async;
|
|
||||||
|
|
||||||
use React\EventLoop\Loop;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class SimpleFiber implements FiberInterface
|
|
||||||
{
|
|
||||||
/** @var ?\Fiber<void,void,void,callable(): mixed> */
|
|
||||||
private static ?\Fiber $scheduler = null;
|
|
||||||
|
|
||||||
private static ?\Closure $suspend = null;
|
|
||||||
|
|
||||||
/** @var ?\Fiber<mixed,mixed,mixed,mixed> */
|
|
||||||
private ?\Fiber $fiber = null;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->fiber = \Fiber::getCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resume(mixed $value): void
|
|
||||||
{
|
|
||||||
if ($this->fiber !== null) {
|
|
||||||
$this->fiber->resume($value);
|
|
||||||
} else {
|
|
||||||
self::$suspend = static fn() => $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
|
|
||||||
$suspend = self::$suspend;
|
|
||||||
self::$suspend = null;
|
|
||||||
|
|
||||||
\Fiber::suspend($suspend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function throw(\Throwable $throwable): void
|
|
||||||
{
|
|
||||||
if ($this->fiber !== null) {
|
|
||||||
$this->fiber->throw($throwable);
|
|
||||||
} else {
|
|
||||||
self::$suspend = static fn() => throw $throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
|
|
||||||
$suspend = self::$suspend;
|
|
||||||
self::$suspend = null;
|
|
||||||
|
|
||||||
\Fiber::suspend($suspend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function suspend(): mixed
|
|
||||||
{
|
|
||||||
if ($this->fiber === null) {
|
|
||||||
if (self::$scheduler === null || self::$scheduler->isTerminated()) {
|
|
||||||
self::$scheduler = new \Fiber(static fn() => Loop::run());
|
|
||||||
// Run event loop to completion on shutdown.
|
|
||||||
\register_shutdown_function(static function (): void {
|
|
||||||
assert(self::$scheduler instanceof \Fiber);
|
|
||||||
if (self::$scheduler->isSuspended()) {
|
|
||||||
self::$scheduler->resume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$ret = (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start());
|
|
||||||
assert(\is_callable($ret));
|
|
||||||
|
|
||||||
return $ret();
|
|
||||||
}
|
|
||||||
|
|
||||||
return \Fiber::suspend();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
846
vendor/react/async/src/functions.php
vendored
846
vendor/react/async/src/functions.php
vendored
@@ -1,846 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Async;
|
|
||||||
|
|
||||||
use React\EventLoop\Loop;
|
|
||||||
use React\EventLoop\TimerInterface;
|
|
||||||
use React\Promise\Deferred;
|
|
||||||
use React\Promise\Promise;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
use function React\Promise\reject;
|
|
||||||
use function React\Promise\resolve;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an async function for a function that uses [`await()`](#await) internally.
|
|
||||||
*
|
|
||||||
* This function is specifically designed to complement the [`await()` function](#await).
|
|
||||||
* The [`await()` function](#await) can be considered *blocking* from the
|
|
||||||
* perspective of the calling code. You can avoid this blocking behavior by
|
|
||||||
* wrapping it in an `async()` function call. Everything inside this function
|
|
||||||
* will still be blocked, but everything outside this function can be executed
|
|
||||||
* asynchronously without blocking:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* Loop::addTimer(0.5, React\Async\async(function () {
|
|
||||||
* echo 'a';
|
|
||||||
* React\Async\await(React\Promise\Timer\sleep(1.0));
|
|
||||||
* echo 'c';
|
|
||||||
* }));
|
|
||||||
*
|
|
||||||
* Loop::addTimer(1.0, function () {
|
|
||||||
* echo 'b';
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // prints "a" at t=0.5s
|
|
||||||
* // prints "b" at t=1.0s
|
|
||||||
* // prints "c" at t=1.5s
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* See also the [`await()` function](#await) for more details.
|
|
||||||
*
|
|
||||||
* Note that this function only works in tandem with the [`await()` function](#await).
|
|
||||||
* In particular, this function does not "magically" make any blocking function
|
|
||||||
* non-blocking:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* Loop::addTimer(0.5, React\Async\async(function () {
|
|
||||||
* echo 'a';
|
|
||||||
* sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
|
|
||||||
* echo 'c';
|
|
||||||
* }));
|
|
||||||
*
|
|
||||||
* Loop::addTimer(1.0, function () {
|
|
||||||
* echo 'b';
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // prints "a" at t=0.5s
|
|
||||||
* // prints "c" at t=1.5s: Correct timing, but wrong order
|
|
||||||
* // prints "b" at t=1.5s: Triggered too late because it was blocked
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* As an alternative, you should always make sure to use this function in tandem
|
|
||||||
* with the [`await()` function](#await) and an async API returning a promise
|
|
||||||
* as shown in the previous example.
|
|
||||||
*
|
|
||||||
* The `async()` function is specifically designed for cases where it is used
|
|
||||||
* as a callback (such as an event loop timer, event listener, or promise
|
|
||||||
* callback). For this reason, it returns a new function wrapping the given
|
|
||||||
* `$function` instead of directly invoking it and returning its value.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* use function React\Async\async;
|
|
||||||
*
|
|
||||||
* Loop::addTimer(1.0, async(function () { … }));
|
|
||||||
* $connection->on('close', async(function () { … }));
|
|
||||||
* $stream->on('data', async(function ($data) { … }));
|
|
||||||
* $promise->then(async(function (int $result) { … }));
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* You can invoke this wrapping function to invoke the given `$function` with
|
|
||||||
* any arguments given as-is. The function will always return a Promise which
|
|
||||||
* will be fulfilled with whatever your `$function` returns. Likewise, it will
|
|
||||||
* return a promise that will be rejected if you throw an `Exception` or
|
|
||||||
* `Throwable` from your `$function`. This allows you to easily create
|
|
||||||
* Promise-based functions:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $promise = React\Async\async(function (): int {
|
|
||||||
* $browser = new React\Http\Browser();
|
|
||||||
* $urls = [
|
|
||||||
* 'https://example.com/alice',
|
|
||||||
* 'https://example.com/bob'
|
|
||||||
* ];
|
|
||||||
*
|
|
||||||
* $bytes = 0;
|
|
||||||
* foreach ($urls as $url) {
|
|
||||||
* $response = React\Async\await($browser->get($url));
|
|
||||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
* $bytes += $response->getBody()->getSize();
|
|
||||||
* }
|
|
||||||
* return $bytes;
|
|
||||||
* })();
|
|
||||||
*
|
|
||||||
* $promise->then(function (int $bytes) {
|
|
||||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
* }, function (Exception $e) {
|
|
||||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The previous example uses [`await()`](#await) inside a loop to highlight how
|
|
||||||
* this vastly simplifies consuming asynchronous operations. At the same time,
|
|
||||||
* this naive example does not leverage concurrent execution, as it will
|
|
||||||
* essentially "await" between each operation. In order to take advantage of
|
|
||||||
* concurrent execution within the given `$function`, you can "await" multiple
|
|
||||||
* promises by using a single [`await()`](#await) together with Promise-based
|
|
||||||
* primitives like this:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $promise = React\Async\async(function (): int {
|
|
||||||
* $browser = new React\Http\Browser();
|
|
||||||
* $urls = [
|
|
||||||
* 'https://example.com/alice',
|
|
||||||
* 'https://example.com/bob'
|
|
||||||
* ];
|
|
||||||
*
|
|
||||||
* $promises = [];
|
|
||||||
* foreach ($urls as $url) {
|
|
||||||
* $promises[] = $browser->get($url);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* try {
|
|
||||||
* $responses = React\Async\await(React\Promise\all($promises));
|
|
||||||
* } catch (Exception $e) {
|
|
||||||
* foreach ($promises as $promise) {
|
|
||||||
* $promise->cancel();
|
|
||||||
* }
|
|
||||||
* throw $e;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* $bytes = 0;
|
|
||||||
* foreach ($responses as $response) {
|
|
||||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
* $bytes += $response->getBody()->getSize();
|
|
||||||
* }
|
|
||||||
* return $bytes;
|
|
||||||
* })();
|
|
||||||
*
|
|
||||||
* $promise->then(function (int $bytes) {
|
|
||||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
* }, function (Exception $e) {
|
|
||||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The returned promise is implemented in such a way that it can be cancelled
|
|
||||||
* when it is still pending. Cancelling a pending promise will cancel any awaited
|
|
||||||
* promises inside that fiber or any nested fibers. As such, the following example
|
|
||||||
* will only output `ab` and cancel the pending [`delay()`](#delay).
|
|
||||||
* The [`await()`](#await) calls in this example would throw a `RuntimeException`
|
|
||||||
* from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $promise = async(static function (): int {
|
|
||||||
* echo 'a';
|
|
||||||
* await(async(static function (): void {
|
|
||||||
* echo 'b';
|
|
||||||
* delay(2);
|
|
||||||
* echo 'c';
|
|
||||||
* })());
|
|
||||||
* echo 'd';
|
|
||||||
*
|
|
||||||
* return time();
|
|
||||||
* })();
|
|
||||||
*
|
|
||||||
* $promise->cancel();
|
|
||||||
* await($promise);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @template T
|
|
||||||
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
|
|
||||||
* @template A2
|
|
||||||
* @template A3
|
|
||||||
* @template A4
|
|
||||||
* @template A5
|
|
||||||
* @param callable(A1,A2,A3,A4,A5): (PromiseInterface<T>|T) $function
|
|
||||||
* @return callable(A1=,A2=,A3=,A4=,A5=): PromiseInterface<T>
|
|
||||||
* @since 4.0.0
|
|
||||||
* @see coroutine()
|
|
||||||
*/
|
|
||||||
function async(callable $function): callable
|
|
||||||
{
|
|
||||||
return static function (mixed ...$args) use ($function): PromiseInterface {
|
|
||||||
$fiber = null;
|
|
||||||
/** @var PromiseInterface<T> $promise*/
|
|
||||||
$promise = new Promise(function (callable $resolve, callable $reject) use ($function, $args, &$fiber): void {
|
|
||||||
$fiber = new \Fiber(function () use ($resolve, $reject, $function, $args, &$fiber): void {
|
|
||||||
try {
|
|
||||||
$resolve($function(...$args));
|
|
||||||
} catch (\Throwable $exception) {
|
|
||||||
$reject($exception);
|
|
||||||
} finally {
|
|
||||||
assert($fiber instanceof \Fiber);
|
|
||||||
FiberMap::unsetPromise($fiber);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$fiber->start();
|
|
||||||
}, function () use (&$fiber): void {
|
|
||||||
assert($fiber instanceof \Fiber);
|
|
||||||
$promise = FiberMap::getPromise($fiber);
|
|
||||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
|
||||||
$promise->cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$lowLevelFiber = \Fiber::getCurrent();
|
|
||||||
if ($lowLevelFiber !== null) {
|
|
||||||
FiberMap::setPromise($lowLevelFiber, $promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $promise;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block waiting for the given `$promise` to be fulfilled.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $result = React\Async\await($promise);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This function will only return after the given `$promise` has settled, i.e.
|
|
||||||
* either fulfilled or rejected. While the promise is pending, this function
|
|
||||||
* can be considered *blocking* from the perspective of the calling code.
|
|
||||||
* You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
|
|
||||||
* call. Everything inside this function will still be blocked, but everything
|
|
||||||
* outside this function can be executed asynchronously without blocking:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* Loop::addTimer(0.5, React\Async\async(function () {
|
|
||||||
* echo 'a';
|
|
||||||
* React\Async\await(React\Promise\Timer\sleep(1.0));
|
|
||||||
* echo 'c';
|
|
||||||
* }));
|
|
||||||
*
|
|
||||||
* Loop::addTimer(1.0, function () {
|
|
||||||
* echo 'b';
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // prints "a" at t=0.5s
|
|
||||||
* // prints "b" at t=1.0s
|
|
||||||
* // prints "c" at t=1.5s
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* See also the [`async()` function](#async) for more details.
|
|
||||||
*
|
|
||||||
* Once the promise is fulfilled, this function will return whatever the promise
|
|
||||||
* resolved to.
|
|
||||||
*
|
|
||||||
* Once the promise is rejected, this will throw whatever the promise rejected
|
|
||||||
* with. If the promise did not reject with an `Exception` or `Throwable`, then
|
|
||||||
* this function will throw an `UnexpectedValueException` instead.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* try {
|
|
||||||
* $result = React\Async\await($promise);
|
|
||||||
* // promise successfully fulfilled with $result
|
|
||||||
* echo 'Result: ' . $result;
|
|
||||||
* } catch (Throwable $e) {
|
|
||||||
* // promise rejected with $e
|
|
||||||
* echo 'Error: ' . $e->getMessage();
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @template T
|
|
||||||
* @param PromiseInterface<T> $promise
|
|
||||||
* @return T returns whatever the promise resolves to
|
|
||||||
* @throws \Exception when the promise is rejected with an `Exception`
|
|
||||||
* @throws \Throwable when the promise is rejected with a `Throwable`
|
|
||||||
* @throws \UnexpectedValueException when the promise is rejected with an unexpected value (Promise API v1 or v2 only)
|
|
||||||
*/
|
|
||||||
function await(PromiseInterface $promise): mixed
|
|
||||||
{
|
|
||||||
$fiber = null;
|
|
||||||
$resolved = false;
|
|
||||||
$rejected = false;
|
|
||||||
|
|
||||||
/** @var T $resolvedValue */
|
|
||||||
$resolvedValue = null;
|
|
||||||
$rejectedThrowable = null;
|
|
||||||
$lowLevelFiber = \Fiber::getCurrent();
|
|
||||||
|
|
||||||
$promise->then(
|
|
||||||
function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFiber): void {
|
|
||||||
if ($lowLevelFiber !== null) {
|
|
||||||
FiberMap::unsetPromise($lowLevelFiber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */
|
|
||||||
if ($fiber === null) {
|
|
||||||
$resolved = true;
|
|
||||||
/** @var T $resolvedValue */
|
|
||||||
$resolvedValue = $value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fiber->resume($value);
|
|
||||||
},
|
|
||||||
function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowLevelFiber): void {
|
|
||||||
if ($lowLevelFiber !== null) {
|
|
||||||
FiberMap::unsetPromise($lowLevelFiber);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$throwable instanceof \Throwable) {
|
|
||||||
$throwable = new \UnexpectedValueException(
|
|
||||||
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable)) /** @phpstan-ignore-line */
|
|
||||||
);
|
|
||||||
|
|
||||||
// avoid garbage references by replacing all closures in call stack.
|
|
||||||
// what a lovely piece of code!
|
|
||||||
$r = new \ReflectionProperty('Exception', 'trace');
|
|
||||||
$trace = $r->getValue($throwable);
|
|
||||||
assert(\is_array($trace));
|
|
||||||
|
|
||||||
// Exception trace arguments only available when zend.exception_ignore_args is not set
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
foreach ($trace as $ti => $one) {
|
|
||||||
if (isset($one['args'])) {
|
|
||||||
foreach ($one['args'] as $ai => $arg) {
|
|
||||||
if ($arg instanceof \Closure) {
|
|
||||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
$r->setValue($throwable, $trace);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($fiber === null) {
|
|
||||||
$rejected = true;
|
|
||||||
$rejectedThrowable = $throwable;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fiber->throw($throwable);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($resolved) {
|
|
||||||
return $resolvedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($rejected) {
|
|
||||||
assert($rejectedThrowable instanceof \Throwable);
|
|
||||||
throw $rejectedThrowable;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($lowLevelFiber !== null) {
|
|
||||||
FiberMap::setPromise($lowLevelFiber, $promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
$fiber = FiberFactory::create();
|
|
||||||
|
|
||||||
return $fiber->suspend();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delay program execution for duration given in `$seconds`.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* React\Async\delay($seconds);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This function will only return after the given number of `$seconds` have
|
|
||||||
* elapsed. If there are no other events attached to this loop, it will behave
|
|
||||||
* similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* echo 'a';
|
|
||||||
* React\Async\delay(1.0);
|
|
||||||
* echo 'b';
|
|
||||||
*
|
|
||||||
* // prints "a" at t=0.0s
|
|
||||||
* // prints "b" at t=1.0s
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
|
|
||||||
* this function may not necessarily halt execution of the entire process thread.
|
|
||||||
* Instead, it allows the event loop to run any other events attached to the
|
|
||||||
* same loop until the delay returns:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* echo 'a';
|
|
||||||
* Loop::addTimer(1.0, function (): void {
|
|
||||||
* echo 'b';
|
|
||||||
* });
|
|
||||||
* React\Async\delay(3.0);
|
|
||||||
* echo 'c';
|
|
||||||
*
|
|
||||||
* // prints "a" at t=0.0s
|
|
||||||
* // prints "b" at t=1.0s
|
|
||||||
* // prints "c" at t=3.0s
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This behavior is especially useful if you want to delay the program execution
|
|
||||||
* of a particular routine, such as when building a simple polling or retry
|
|
||||||
* mechanism:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* try {
|
|
||||||
* something();
|
|
||||||
* } catch (Throwable) {
|
|
||||||
* // in case of error, retry after a short delay
|
|
||||||
* React\Async\delay(1.0);
|
|
||||||
* something();
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Because this function only returns after some time has passed, it can be
|
|
||||||
* considered *blocking* from the perspective of the calling code. You can avoid
|
|
||||||
* this blocking behavior by wrapping it in an [`async()` function](#async) call.
|
|
||||||
* Everything inside this function will still be blocked, but everything outside
|
|
||||||
* this function can be executed asynchronously without blocking:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* Loop::addTimer(0.5, React\Async\async(function (): void {
|
|
||||||
* echo 'a';
|
|
||||||
* React\Async\delay(1.0);
|
|
||||||
* echo 'c';
|
|
||||||
* }));
|
|
||||||
*
|
|
||||||
* Loop::addTimer(1.0, function (): void {
|
|
||||||
* echo 'b';
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // prints "a" at t=0.5s
|
|
||||||
* // prints "b" at t=1.0s
|
|
||||||
* // prints "c" at t=1.5s
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* See also the [`async()` function](#async) for more details.
|
|
||||||
*
|
|
||||||
* Internally, the `$seconds` argument will be used as a timer for the loop so that
|
|
||||||
* it keeps running until this timer triggers. This implies that if you pass a
|
|
||||||
* really small (or negative) value, it will still start a timer and will thus
|
|
||||||
* trigger at the earliest possible time in the future.
|
|
||||||
*
|
|
||||||
* The function is implemented in such a way that it can be cancelled when it is
|
|
||||||
* running inside an [`async()` function](#async). Cancelling the resulting
|
|
||||||
* promise will clean up any pending timers and throw a `RuntimeException` from
|
|
||||||
* the pending delay which in turn would reject the resulting promise.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $promise = async(function (): void {
|
|
||||||
* echo 'a';
|
|
||||||
* delay(3.0);
|
|
||||||
* echo 'b';
|
|
||||||
* })();
|
|
||||||
*
|
|
||||||
* Loop::addTimer(2.0, function () use ($promise): void {
|
|
||||||
* $promise->cancel();
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // prints "a" at t=0.0s
|
|
||||||
* // rejects $promise at t=2.0
|
|
||||||
* // never prints "b"
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
* @throws \RuntimeException when the function is cancelled inside an `async()` function
|
|
||||||
* @see async()
|
|
||||||
* @uses await()
|
|
||||||
*/
|
|
||||||
function delay(float $seconds): void
|
|
||||||
{
|
|
||||||
/** @var ?TimerInterface $timer */
|
|
||||||
$timer = null;
|
|
||||||
|
|
||||||
await(new Promise(function (callable $resolve) use ($seconds, &$timer): void {
|
|
||||||
$timer = Loop::addTimer($seconds, fn() => $resolve(null));
|
|
||||||
}, function () use (&$timer): void {
|
|
||||||
assert($timer instanceof TimerInterface);
|
|
||||||
Loop::cancelTimer($timer);
|
|
||||||
throw new \RuntimeException('Delay cancelled');
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a Generator-based coroutine to "await" promises.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* React\Async\coroutine(function () {
|
|
||||||
* $browser = new React\Http\Browser();
|
|
||||||
*
|
|
||||||
* try {
|
|
||||||
* $response = yield $browser->get('https://example.com/');
|
|
||||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
* echo $response->getBody();
|
|
||||||
* } catch (Exception $e) {
|
|
||||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Using Generator-based coroutines is an alternative to directly using the
|
|
||||||
* underlying promise APIs. For many use cases, this makes using promise-based
|
|
||||||
* APIs much simpler, as it resembles a synchronous code flow more closely.
|
|
||||||
* The above example performs the equivalent of directly using the promise APIs:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $browser = new React\Http\Browser();
|
|
||||||
*
|
|
||||||
* $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
|
|
||||||
* echo $response->getBody();
|
|
||||||
* }, function (Exception $e) {
|
|
||||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The `yield` keyword can be used to "await" a promise resolution. Internally,
|
|
||||||
* it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
|
|
||||||
* This allows the execution to be interrupted and resumed at the same place
|
|
||||||
* when the promise is fulfilled. The `yield` statement returns whatever the
|
|
||||||
* promise is fulfilled with. If the promise is rejected, it will throw an
|
|
||||||
* `Exception` or `Throwable`.
|
|
||||||
*
|
|
||||||
* The `coroutine()` function will always return a Promise which will be
|
|
||||||
* fulfilled with whatever your `$function` returns. Likewise, it will return
|
|
||||||
* a promise that will be rejected if you throw an `Exception` or `Throwable`
|
|
||||||
* from your `$function`. This allows you to easily create Promise-based
|
|
||||||
* functions:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $promise = React\Async\coroutine(function () {
|
|
||||||
* $browser = new React\Http\Browser();
|
|
||||||
* $urls = [
|
|
||||||
* 'https://example.com/alice',
|
|
||||||
* 'https://example.com/bob'
|
|
||||||
* ];
|
|
||||||
*
|
|
||||||
* $bytes = 0;
|
|
||||||
* foreach ($urls as $url) {
|
|
||||||
* $response = yield $browser->get($url);
|
|
||||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
* $bytes += $response->getBody()->getSize();
|
|
||||||
* }
|
|
||||||
* return $bytes;
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* $promise->then(function (int $bytes) {
|
|
||||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
* }, function (Exception $e) {
|
|
||||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The previous example uses a `yield` statement inside a loop to highlight how
|
|
||||||
* this vastly simplifies consuming asynchronous operations. At the same time,
|
|
||||||
* this naive example does not leverage concurrent execution, as it will
|
|
||||||
* essentially "await" between each operation. In order to take advantage of
|
|
||||||
* concurrent execution within the given `$function`, you can "await" multiple
|
|
||||||
* promises by using a single `yield` together with Promise-based primitives
|
|
||||||
* like this:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $promise = React\Async\coroutine(function () {
|
|
||||||
* $browser = new React\Http\Browser();
|
|
||||||
* $urls = [
|
|
||||||
* 'https://example.com/alice',
|
|
||||||
* 'https://example.com/bob'
|
|
||||||
* ];
|
|
||||||
*
|
|
||||||
* $promises = [];
|
|
||||||
* foreach ($urls as $url) {
|
|
||||||
* $promises[] = $browser->get($url);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* try {
|
|
||||||
* $responses = yield React\Promise\all($promises);
|
|
||||||
* } catch (Exception $e) {
|
|
||||||
* foreach ($promises as $promise) {
|
|
||||||
* $promise->cancel();
|
|
||||||
* }
|
|
||||||
* throw $e;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* $bytes = 0;
|
|
||||||
* foreach ($responses as $response) {
|
|
||||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
|
||||||
* $bytes += $response->getBody()->getSize();
|
|
||||||
* }
|
|
||||||
* return $bytes;
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* $promise->then(function (int $bytes) {
|
|
||||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
|
||||||
* }, function (Exception $e) {
|
|
||||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @template T
|
|
||||||
* @template TYield
|
|
||||||
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
|
|
||||||
* @template A2
|
|
||||||
* @template A3
|
|
||||||
* @template A4
|
|
||||||
* @template A5
|
|
||||||
* @param callable(A1, A2, A3, A4, A5):(\Generator<mixed, PromiseInterface<TYield>, TYield, PromiseInterface<T>|T>|PromiseInterface<T>|T) $function
|
|
||||||
* @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is
|
|
||||||
* @return PromiseInterface<T>
|
|
||||||
* @since 3.0.0
|
|
||||||
*/
|
|
||||||
function coroutine(callable $function, mixed ...$args): PromiseInterface
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$generator = $function(...$args);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return reject($e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$generator instanceof \Generator) {
|
|
||||||
return resolve($generator);
|
|
||||||
}
|
|
||||||
|
|
||||||
$promise = null;
|
|
||||||
/** @var Deferred<T> $deferred*/
|
|
||||||
$deferred = new Deferred(function () use (&$promise) {
|
|
||||||
/** @var ?PromiseInterface<T> $promise */
|
|
||||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
|
||||||
$promise->cancel();
|
|
||||||
}
|
|
||||||
$promise = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @var callable $next */
|
|
||||||
$next = function () use ($deferred, $generator, &$next, &$promise) {
|
|
||||||
try {
|
|
||||||
if (!$generator->valid()) {
|
|
||||||
$next = null;
|
|
||||||
$deferred->resolve($generator->getReturn());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$next = null;
|
|
||||||
$deferred->reject($e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$promise = $generator->current();
|
|
||||||
if (!$promise instanceof PromiseInterface) {
|
|
||||||
$next = null;
|
|
||||||
$deferred->reject(new \UnexpectedValueException(
|
|
||||||
'Expected coroutine to yield ' . PromiseInterface::class . ', but got ' . (is_object($promise) ? get_class($promise) : gettype($promise))
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var PromiseInterface<TYield> $promise */
|
|
||||||
assert($next instanceof \Closure);
|
|
||||||
$promise->then(function ($value) use ($generator, $next) {
|
|
||||||
$generator->send($value);
|
|
||||||
$next();
|
|
||||||
}, function (\Throwable $reason) use ($generator, $next) {
|
|
||||||
$generator->throw($reason);
|
|
||||||
$next();
|
|
||||||
})->then(null, function (\Throwable $reason) use ($deferred, &$next) {
|
|
||||||
$next = null;
|
|
||||||
$deferred->reject($reason);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
$next();
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
|
|
||||||
* @return PromiseInterface<array<T>>
|
|
||||||
*/
|
|
||||||
function parallel(iterable $tasks): PromiseInterface
|
|
||||||
{
|
|
||||||
/** @var array<int,PromiseInterface<T>> $pending */
|
|
||||||
$pending = [];
|
|
||||||
/** @var Deferred<array<T>> $deferred */
|
|
||||||
$deferred = new Deferred(function () use (&$pending) {
|
|
||||||
foreach ($pending as $promise) {
|
|
||||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
|
||||||
$promise->cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$pending = [];
|
|
||||||
});
|
|
||||||
$results = [];
|
|
||||||
$continue = true;
|
|
||||||
|
|
||||||
$taskErrback = function ($error) use (&$pending, $deferred, &$continue) {
|
|
||||||
$continue = false;
|
|
||||||
$deferred->reject($error);
|
|
||||||
|
|
||||||
foreach ($pending as $promise) {
|
|
||||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
|
||||||
$promise->cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$pending = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach ($tasks as $i => $task) {
|
|
||||||
$taskCallback = function ($result) use (&$results, &$pending, &$continue, $i, $deferred) {
|
|
||||||
$results[$i] = $result;
|
|
||||||
unset($pending[$i]);
|
|
||||||
|
|
||||||
if (!$pending && !$continue) {
|
|
||||||
$deferred->resolve($results);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$promise = \call_user_func($task);
|
|
||||||
assert($promise instanceof PromiseInterface);
|
|
||||||
$pending[$i] = $promise;
|
|
||||||
|
|
||||||
$promise->then($taskCallback, $taskErrback);
|
|
||||||
|
|
||||||
if (!$continue) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$continue = false;
|
|
||||||
if (!$pending) {
|
|
||||||
$deferred->resolve($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
|
|
||||||
* @return PromiseInterface<array<T>>
|
|
||||||
*/
|
|
||||||
function series(iterable $tasks): PromiseInterface
|
|
||||||
{
|
|
||||||
$pending = null;
|
|
||||||
/** @var Deferred<array<T>> $deferred */
|
|
||||||
$deferred = new Deferred(function () use (&$pending) {
|
|
||||||
/** @var ?PromiseInterface<T> $pending */
|
|
||||||
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
|
|
||||||
$pending->cancel();
|
|
||||||
}
|
|
||||||
$pending = null;
|
|
||||||
});
|
|
||||||
$results = [];
|
|
||||||
|
|
||||||
if ($tasks instanceof \IteratorAggregate) {
|
|
||||||
$tasks = $tasks->getIterator();
|
|
||||||
assert($tasks instanceof \Iterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
$taskCallback = function ($result) use (&$results, &$next) {
|
|
||||||
$results[] = $result;
|
|
||||||
/** @var \Closure $next */
|
|
||||||
$next();
|
|
||||||
};
|
|
||||||
|
|
||||||
$next = function () use (&$tasks, $taskCallback, $deferred, &$results, &$pending) {
|
|
||||||
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
|
|
||||||
$deferred->resolve($results);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tasks instanceof \Iterator) {
|
|
||||||
$task = $tasks->current();
|
|
||||||
$tasks->next();
|
|
||||||
} else {
|
|
||||||
assert(\is_array($tasks));
|
|
||||||
$task = \array_shift($tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(\is_callable($task));
|
|
||||||
$promise = \call_user_func($task);
|
|
||||||
assert($promise instanceof PromiseInterface);
|
|
||||||
$pending = $promise;
|
|
||||||
|
|
||||||
$promise->then($taskCallback, array($deferred, 'reject'));
|
|
||||||
};
|
|
||||||
|
|
||||||
$next();
|
|
||||||
|
|
||||||
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param iterable<(callable():(PromiseInterface<T>|T))|(callable(mixed):(PromiseInterface<T>|T))> $tasks
|
|
||||||
* @return PromiseInterface<($tasks is non-empty-array|\Traversable ? T : null)>
|
|
||||||
*/
|
|
||||||
function waterfall(iterable $tasks): PromiseInterface
|
|
||||||
{
|
|
||||||
$pending = null;
|
|
||||||
/** @var Deferred<T> $deferred*/
|
|
||||||
$deferred = new Deferred(function () use (&$pending) {
|
|
||||||
/** @var ?PromiseInterface<T> $pending */
|
|
||||||
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
|
|
||||||
$pending->cancel();
|
|
||||||
}
|
|
||||||
$pending = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($tasks instanceof \IteratorAggregate) {
|
|
||||||
$tasks = $tasks->getIterator();
|
|
||||||
assert($tasks instanceof \Iterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var callable $next */
|
|
||||||
$next = function ($value = null) use (&$tasks, &$next, $deferred, &$pending) {
|
|
||||||
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
|
|
||||||
$deferred->resolve($value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tasks instanceof \Iterator) {
|
|
||||||
$task = $tasks->current();
|
|
||||||
$tasks->next();
|
|
||||||
} else {
|
|
||||||
assert(\is_array($tasks));
|
|
||||||
$task = \array_shift($tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(\is_callable($task));
|
|
||||||
$promise = \call_user_func_array($task, func_get_args());
|
|
||||||
assert($promise instanceof PromiseInterface);
|
|
||||||
$pending = $promise;
|
|
||||||
|
|
||||||
$promise->then($next, array($deferred, 'reject'));
|
|
||||||
};
|
|
||||||
|
|
||||||
$next();
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
9
vendor/react/async/src/functions_include.php
vendored
9
vendor/react/async/src/functions_include.php
vendored
@@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Async;
|
|
||||||
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
if (!\function_exists(__NAMESPACE__ . '\\parallel')) {
|
|
||||||
require __DIR__ . '/functions.php';
|
|
||||||
}
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
96
vendor/react/cache/CHANGELOG.md
vendored
96
vendor/react/cache/CHANGELOG.md
vendored
@@ -1,96 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## 1.2.0 (2022-11-30)
|
|
||||||
|
|
||||||
* Feature: Support PHP 8.1 and PHP 8.2.
|
|
||||||
(#47 by @SimonFrings and #52 by @WyriHaximus)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#48 by @SimonFrings and #51 by @nhedger)
|
|
||||||
|
|
||||||
* Update test suite and use GitHub actions for continuous integration (CI).
|
|
||||||
(#45 and #49 by @SimonFrings and #54 by @clue)
|
|
||||||
|
|
||||||
## 1.1.0 (2020-09-18)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with react/promise 3.
|
|
||||||
(#39 by @WyriHaximus)
|
|
||||||
|
|
||||||
* Add `.gitattributes` to exclude dev files from exports.
|
|
||||||
(#40 by @reedy)
|
|
||||||
|
|
||||||
* Improve test suite, update to support PHP 8 and PHPUnit 9.3.
|
|
||||||
(#41 and #43 by @SimonFrings and #42 by @WyriHaximus)
|
|
||||||
|
|
||||||
## 1.0.0 (2019-07-11)
|
|
||||||
|
|
||||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
|
||||||
We'd like to emphasize that this component is production ready and battle-tested.
|
|
||||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
|
||||||
so you have a rock-solid foundation to build on top of.
|
|
||||||
|
|
||||||
> Contains no other changes, so it's actually fully compatible with the v0.6.0 release.
|
|
||||||
|
|
||||||
## 0.6.0 (2019-07-04)
|
|
||||||
|
|
||||||
* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()`
|
|
||||||
supporting multiple cache items (inspired by PSR-16).
|
|
||||||
(#32 by @krlv and #37 by @clue)
|
|
||||||
|
|
||||||
* Documentation for TTL precision with millisecond accuracy or below and
|
|
||||||
use high-resolution timer for cache TTL on PHP 7.3+.
|
|
||||||
(#35 and #38 by @clue)
|
|
||||||
|
|
||||||
* Improve API documentation and allow legacy HHVM to fail in Travis CI config.
|
|
||||||
(#34 and #36 by @clue)
|
|
||||||
|
|
||||||
* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
|
|
||||||
(#31 by @WyriHaximus)
|
|
||||||
|
|
||||||
## 0.5.0 (2018-06-25)
|
|
||||||
|
|
||||||
* Improve documentation by describing what is expected of a class implementing `CacheInterface`.
|
|
||||||
(#21, #22, #23, #27 by @WyriHaximus)
|
|
||||||
|
|
||||||
* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`.
|
|
||||||
(#26 by @clue)
|
|
||||||
|
|
||||||
* Added support for cache expiration (TTL).
|
|
||||||
(#29 by @clue and @WyriHaximus)
|
|
||||||
|
|
||||||
* Renamed `remove` to `delete` making it more in line with `PSR-16`.
|
|
||||||
(#30 by @clue)
|
|
||||||
|
|
||||||
## 0.4.2 (2017-12-20)
|
|
||||||
|
|
||||||
* Improve documentation with usage and installation instructions
|
|
||||||
(#10 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite by adding PHPUnit to `require-dev` and
|
|
||||||
add forward compatibility with PHPUnit 5 and PHPUnit 6 and
|
|
||||||
sanitize Composer autoload paths
|
|
||||||
(#14 by @shaunbramley and #12 and #18 by @clue)
|
|
||||||
|
|
||||||
## 0.4.1 (2016-02-25)
|
|
||||||
|
|
||||||
* Repository maintenance, split off from main repo, improve test suite and documentation
|
|
||||||
* First class support for PHP7 and HHVM (#9 by @clue)
|
|
||||||
* Adjust compatibility to 5.3 (#7 by @clue)
|
|
||||||
|
|
||||||
## 0.4.0 (2014-02-02)
|
|
||||||
|
|
||||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
|
||||||
* BC break: Update to React/Promise 2.0
|
|
||||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
|
||||||
|
|
||||||
## 0.3.2 (2013-05-10)
|
|
||||||
|
|
||||||
* Version bump
|
|
||||||
|
|
||||||
## 0.3.0 (2013-04-14)
|
|
||||||
|
|
||||||
* Version bump
|
|
||||||
|
|
||||||
## 0.2.6 (2012-12-26)
|
|
||||||
|
|
||||||
* Feature: New cache component, used by DNS
|
|
||||||
21
vendor/react/cache/LICENSE
vendored
21
vendor/react/cache/LICENSE
vendored
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
|
||||||
to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
367
vendor/react/cache/README.md
vendored
367
vendor/react/cache/README.md
vendored
@@ -1,367 +0,0 @@
|
|||||||
# Cache
|
|
||||||
|
|
||||||
[](https://github.com/reactphp/cache/actions)
|
|
||||||
[](https://packagist.org/packages/react/cache)
|
|
||||||
|
|
||||||
Async, [Promise](https://github.com/reactphp/promise)-based cache interface
|
|
||||||
for [ReactPHP](https://reactphp.org/).
|
|
||||||
|
|
||||||
The cache component provides a
|
|
||||||
[Promise](https://github.com/reactphp/promise)-based
|
|
||||||
[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
|
|
||||||
implementation of that.
|
|
||||||
This allows consumers to type hint against the interface and third parties to
|
|
||||||
provide alternate implementations.
|
|
||||||
This project is heavily inspired by
|
|
||||||
[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/),
|
|
||||||
but uses an interface more suited for async, non-blocking applications.
|
|
||||||
|
|
||||||
**Table of Contents**
|
|
||||||
|
|
||||||
* [Usage](#usage)
|
|
||||||
* [CacheInterface](#cacheinterface)
|
|
||||||
* [get()](#get)
|
|
||||||
* [set()](#set)
|
|
||||||
* [delete()](#delete)
|
|
||||||
* [getMultiple()](#getmultiple)
|
|
||||||
* [setMultiple()](#setmultiple)
|
|
||||||
* [deleteMultiple()](#deletemultiple)
|
|
||||||
* [clear()](#clear)
|
|
||||||
* [has()](#has)
|
|
||||||
* [ArrayCache](#arraycache)
|
|
||||||
* [Common usage](#common-usage)
|
|
||||||
* [Fallback get](#fallback-get)
|
|
||||||
* [Fallback-get-and-set](#fallback-get-and-set)
|
|
||||||
* [Install](#install)
|
|
||||||
* [Tests](#tests)
|
|
||||||
* [License](#license)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### CacheInterface
|
|
||||||
|
|
||||||
The `CacheInterface` describes the main interface of this component.
|
|
||||||
This allows consumers to type hint against the interface and third parties to
|
|
||||||
provide alternate implementations.
|
|
||||||
|
|
||||||
#### get()
|
|
||||||
|
|
||||||
The `get(string $key, mixed $default = null): PromiseInterface<mixed>` method can be used to
|
|
||||||
retrieve an item from the cache.
|
|
||||||
|
|
||||||
This method will resolve with the cached value on success or with the
|
|
||||||
given `$default` value when no item can be found or when an error occurs.
|
|
||||||
Similarly, an expired cache item (once the time-to-live is expired) is
|
|
||||||
considered a cache miss.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache
|
|
||||||
->get('foo')
|
|
||||||
->then('var_dump');
|
|
||||||
```
|
|
||||||
|
|
||||||
This example fetches the value of the key `foo` and passes it to the
|
|
||||||
`var_dump` function. You can use any of the composition provided by
|
|
||||||
[promises](https://github.com/reactphp/promise).
|
|
||||||
|
|
||||||
#### set()
|
|
||||||
|
|
||||||
The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface<bool>` method can be used to
|
|
||||||
store an item in the cache.
|
|
||||||
|
|
||||||
This method will resolve with `true` on success or `false` when an error
|
|
||||||
occurs. If the cache implementation has to go over the network to store
|
|
||||||
it, it may take a while.
|
|
||||||
|
|
||||||
The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
|
||||||
for this cache item. If this parameter is omitted (or `null`), the item
|
|
||||||
will stay in the cache for as long as the underlying implementation
|
|
||||||
supports. Trying to access an expired cache item results in a cache miss,
|
|
||||||
see also [`get()`](#get).
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache->set('foo', 'bar', 60);
|
|
||||||
```
|
|
||||||
|
|
||||||
This example eventually sets the value of the key `foo` to `bar`. If it
|
|
||||||
already exists, it is overridden.
|
|
||||||
|
|
||||||
This interface does not enforce any particular TTL resolution, so special
|
|
||||||
care may have to be taken if you rely on very high precision with
|
|
||||||
millisecond accuracy or below. Cache implementations SHOULD work on a
|
|
||||||
best effort basis and SHOULD provide at least second accuracy unless
|
|
||||||
otherwise noted. Many existing cache implementations are known to provide
|
|
||||||
microsecond or millisecond accuracy, but it's generally not recommended
|
|
||||||
to rely on this high precision.
|
|
||||||
|
|
||||||
This interface suggests that cache implementations SHOULD use a monotonic
|
|
||||||
time source if available. Given that a monotonic time source is only
|
|
||||||
available as of PHP 7.3 by default, cache implementations MAY fall back
|
|
||||||
to using wall-clock time.
|
|
||||||
While this does not affect many common use cases, this is an important
|
|
||||||
distinction for programs that rely on a high time precision or on systems
|
|
||||||
that are subject to discontinuous time adjustments (time jumps).
|
|
||||||
This means that if you store a cache item with a TTL of 30s and then
|
|
||||||
adjust your system time forward by 20s, the cache item SHOULD still
|
|
||||||
expire in 30s.
|
|
||||||
|
|
||||||
#### delete()
|
|
||||||
|
|
||||||
The `delete(string $key): PromiseInterface<bool>` method can be used to
|
|
||||||
delete an item from the cache.
|
|
||||||
|
|
||||||
This method will resolve with `true` on success or `false` when an error
|
|
||||||
occurs. When no item for `$key` is found in the cache, it also resolves
|
|
||||||
to `true`. If the cache implementation has to go over the network to
|
|
||||||
delete it, it may take a while.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache->delete('foo');
|
|
||||||
```
|
|
||||||
|
|
||||||
This example eventually deletes the key `foo` from the cache. As with
|
|
||||||
`set()`, this may not happen instantly and a promise is returned to
|
|
||||||
provide guarantees whether or not the item has been removed from cache.
|
|
||||||
|
|
||||||
#### getMultiple()
|
|
||||||
|
|
||||||
The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface<array>` method can be used to
|
|
||||||
retrieve multiple cache items by their unique keys.
|
|
||||||
|
|
||||||
This method will resolve with an array of cached values on success or with the
|
|
||||||
given `$default` value when an item can not be found or when an error occurs.
|
|
||||||
Similarly, an expired cache item (once the time-to-live is expired) is
|
|
||||||
considered a cache miss.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache->getMultiple(array('name', 'age'))->then(function (array $values) {
|
|
||||||
$name = $values['name'] ?? 'User';
|
|
||||||
$age = $values['age'] ?? 'n/a';
|
|
||||||
|
|
||||||
echo $name . ' is ' . $age . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
This example fetches the cache items for the `name` and `age` keys and
|
|
||||||
prints some example output. You can use any of the composition provided
|
|
||||||
by [promises](https://github.com/reactphp/promise).
|
|
||||||
|
|
||||||
#### setMultiple()
|
|
||||||
|
|
||||||
The `setMultiple(array $values, ?float $ttl = null): PromiseInterface<bool>` method can be used to
|
|
||||||
persist a set of key => value pairs in the cache, with an optional TTL.
|
|
||||||
|
|
||||||
This method will resolve with `true` on success or `false` when an error
|
|
||||||
occurs. If the cache implementation has to go over the network to store
|
|
||||||
it, it may take a while.
|
|
||||||
|
|
||||||
The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
|
||||||
for these cache items. If this parameter is omitted (or `null`), these items
|
|
||||||
will stay in the cache for as long as the underlying implementation
|
|
||||||
supports. Trying to access an expired cache items results in a cache miss,
|
|
||||||
see also [`getMultiple()`](#getmultiple).
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
|
|
||||||
```
|
|
||||||
|
|
||||||
This example eventually sets the list of values - the key `foo` to `1` value
|
|
||||||
and the key `bar` to `2`. If some of the keys already exist, they are overridden.
|
|
||||||
|
|
||||||
#### deleteMultiple()
|
|
||||||
|
|
||||||
The `setMultiple(string[] $keys): PromiseInterface<bool>` method can be used to
|
|
||||||
delete multiple cache items in a single operation.
|
|
||||||
|
|
||||||
This method will resolve with `true` on success or `false` when an error
|
|
||||||
occurs. When no items for `$keys` are found in the cache, it also resolves
|
|
||||||
to `true`. If the cache implementation has to go over the network to
|
|
||||||
delete it, it may take a while.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache->deleteMultiple(array('foo', 'bar, 'baz'));
|
|
||||||
```
|
|
||||||
|
|
||||||
This example eventually deletes keys `foo`, `bar` and `baz` from the cache.
|
|
||||||
As with `setMultiple()`, this may not happen instantly and a promise is returned to
|
|
||||||
provide guarantees whether or not the item has been removed from cache.
|
|
||||||
|
|
||||||
#### clear()
|
|
||||||
|
|
||||||
The `clear(): PromiseInterface<bool>` method can be used to
|
|
||||||
wipe clean the entire cache.
|
|
||||||
|
|
||||||
This method will resolve with `true` on success or `false` when an error
|
|
||||||
occurs. If the cache implementation has to go over the network to
|
|
||||||
delete it, it may take a while.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache->clear();
|
|
||||||
```
|
|
||||||
|
|
||||||
This example eventually deletes all keys from the cache. As with `deleteMultiple()`,
|
|
||||||
this may not happen instantly and a promise is returned to provide guarantees
|
|
||||||
whether or not all the items have been removed from cache.
|
|
||||||
|
|
||||||
#### has()
|
|
||||||
|
|
||||||
The `has(string $key): PromiseInterface<bool>` method can be used to
|
|
||||||
determine whether an item is present in the cache.
|
|
||||||
|
|
||||||
This method will resolve with `true` on success or `false` when no item can be found
|
|
||||||
or when an error occurs. Similarly, an expired cache item (once the time-to-live
|
|
||||||
is expired) is considered a cache miss.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache
|
|
||||||
->has('foo')
|
|
||||||
->then('var_dump');
|
|
||||||
```
|
|
||||||
|
|
||||||
This example checks if the value of the key `foo` is set in the cache and passes
|
|
||||||
the result to the `var_dump` function. You can use any of the composition provided by
|
|
||||||
[promises](https://github.com/reactphp/promise).
|
|
||||||
|
|
||||||
NOTE: It is recommended that has() is only to be used for cache warming type purposes
|
|
||||||
and not to be used within your live applications operations for get/set, as this method
|
|
||||||
is subject to a race condition where your has() will return true and immediately after,
|
|
||||||
another script can remove it making the state of your app out of date.
|
|
||||||
|
|
||||||
### ArrayCache
|
|
||||||
|
|
||||||
The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache = new ArrayCache();
|
|
||||||
|
|
||||||
$cache->set('foo', 'bar');
|
|
||||||
```
|
|
||||||
|
|
||||||
Its constructor accepts an optional `?int $limit` parameter to limit the
|
|
||||||
maximum number of entries to store in the LRU cache. If you add more
|
|
||||||
entries to this instance, it will automatically take care of removing
|
|
||||||
the one that was least recently used (LRU).
|
|
||||||
|
|
||||||
For example, this snippet will overwrite the first value and only store
|
|
||||||
the last two entries:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache = new ArrayCache(2);
|
|
||||||
|
|
||||||
$cache->set('foo', '1');
|
|
||||||
$cache->set('bar', '2');
|
|
||||||
$cache->set('baz', '3');
|
|
||||||
```
|
|
||||||
|
|
||||||
This cache implementation is known to rely on wall-clock time to schedule
|
|
||||||
future cache expiration times when using any version before PHP 7.3,
|
|
||||||
because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
|
|
||||||
While this does not affect many common use cases, this is an important
|
|
||||||
distinction for programs that rely on a high time precision or on systems
|
|
||||||
that are subject to discontinuous time adjustments (time jumps).
|
|
||||||
This means that if you store a cache item with a TTL of 30s on PHP < 7.3
|
|
||||||
and then adjust your system time forward by 20s, the cache item may
|
|
||||||
expire in 10s. See also [`set()`](#set) for more details.
|
|
||||||
|
|
||||||
## Common usage
|
|
||||||
|
|
||||||
### Fallback get
|
|
||||||
|
|
||||||
A common use case of caches is to attempt fetching a cached value and as a
|
|
||||||
fallback retrieve it from the original data source if not found. Here is an
|
|
||||||
example of that:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache
|
|
||||||
->get('foo')
|
|
||||||
->then(function ($result) {
|
|
||||||
if ($result === null) {
|
|
||||||
return getFooFromDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
})
|
|
||||||
->then('var_dump');
|
|
||||||
```
|
|
||||||
|
|
||||||
First an attempt is made to retrieve the value of `foo`. A callback function is
|
|
||||||
registered that will call `getFooFromDb` when the resulting value is null.
|
|
||||||
`getFooFromDb` is a function (can be any PHP callable) that will be called if the
|
|
||||||
key does not exist in the cache.
|
|
||||||
|
|
||||||
`getFooFromDb` can handle the missing key by returning a promise for the
|
|
||||||
actual value from the database (or any other data source). As a result, this
|
|
||||||
chain will correctly fall back, and provide the value in both cases.
|
|
||||||
|
|
||||||
### Fallback get and set
|
|
||||||
|
|
||||||
To expand on the fallback get example, often you want to set the value on the
|
|
||||||
cache after fetching it from the data source.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache
|
|
||||||
->get('foo')
|
|
||||||
->then(function ($result) {
|
|
||||||
if ($result === null) {
|
|
||||||
return $this->getAndCacheFooFromDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
})
|
|
||||||
->then('var_dump');
|
|
||||||
|
|
||||||
public function getAndCacheFooFromDb()
|
|
||||||
{
|
|
||||||
return $this->db
|
|
||||||
->get('foo')
|
|
||||||
->then(array($this, 'cacheFooFromDb'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cacheFooFromDb($foo)
|
|
||||||
{
|
|
||||||
$this->cache->set('foo', $foo);
|
|
||||||
|
|
||||||
return $foo;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
By using chaining you can easily conditionally cache the value if it is
|
|
||||||
fetched from the database.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
The recommended way to install this library is [through Composer](https://getcomposer.org).
|
|
||||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
|
||||||
|
|
||||||
This project follows [SemVer](https://semver.org/).
|
|
||||||
This will install the latest supported version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer require react/cache:^1.2
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
|
||||||
|
|
||||||
This project aims to run on any platform and thus does not require any PHP
|
|
||||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
|
||||||
HHVM.
|
|
||||||
It's *highly recommended to use PHP 7+* for this project.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
To run the test suite, you first need to clone this repo and then install all
|
|
||||||
dependencies [through Composer](https://getcomposer.org):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer install
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the test suite, go to the project root and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vendor/bin/phpunit
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT, see [LICENSE file](LICENSE).
|
|
||||||
45
vendor/react/cache/composer.json
vendored
45
vendor/react/cache/composer.json
vendored
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "react/cache",
|
|
||||||
"description": "Async, Promise-based cache interface for ReactPHP",
|
|
||||||
"keywords": ["cache", "caching", "promise", "ReactPHP"],
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Christian Lück",
|
|
||||||
"homepage": "https://clue.engineering/",
|
|
||||||
"email": "christian@clue.engineering"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cees-Jan Kiewiet",
|
|
||||||
"homepage": "https://wyrihaximus.net/",
|
|
||||||
"email": "reactphp@ceesjankiewiet.nl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jan Sorgalla",
|
|
||||||
"homepage": "https://sorgalla.com/",
|
|
||||||
"email": "jsorgalla@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Chris Boden",
|
|
||||||
"homepage": "https://cboden.dev/",
|
|
||||||
"email": "cboden@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.3.0",
|
|
||||||
"react/promise": "^3.0 || ^2.0 || ^1.1"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"React\\Cache\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload-dev": {
|
|
||||||
"psr-4": {
|
|
||||||
"React\\Tests\\Cache\\": "tests/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
181
vendor/react/cache/src/ArrayCache.php
vendored
181
vendor/react/cache/src/ArrayCache.php
vendored
@@ -1,181 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Cache;
|
|
||||||
|
|
||||||
use React\Promise;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
class ArrayCache implements CacheInterface
|
|
||||||
{
|
|
||||||
private $limit;
|
|
||||||
private $data = array();
|
|
||||||
private $expires = array();
|
|
||||||
private $supportsHighResolution;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache = new ArrayCache();
|
|
||||||
*
|
|
||||||
* $cache->set('foo', 'bar');
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Its constructor accepts an optional `?int $limit` parameter to limit the
|
|
||||||
* maximum number of entries to store in the LRU cache. If you add more
|
|
||||||
* entries to this instance, it will automatically take care of removing
|
|
||||||
* the one that was least recently used (LRU).
|
|
||||||
*
|
|
||||||
* For example, this snippet will overwrite the first value and only store
|
|
||||||
* the last two entries:
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache = new ArrayCache(2);
|
|
||||||
*
|
|
||||||
* $cache->set('foo', '1');
|
|
||||||
* $cache->set('bar', '2');
|
|
||||||
* $cache->set('baz', '3');
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This cache implementation is known to rely on wall-clock time to schedule
|
|
||||||
* future cache expiration times when using any version before PHP 7.3,
|
|
||||||
* because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
|
|
||||||
* While this does not affect many common use cases, this is an important
|
|
||||||
* distinction for programs that rely on a high time precision or on systems
|
|
||||||
* that are subject to discontinuous time adjustments (time jumps).
|
|
||||||
* This means that if you store a cache item with a TTL of 30s on PHP < 7.3
|
|
||||||
* and then adjust your system time forward by 20s, the cache item may
|
|
||||||
* expire in 10s. See also [`set()`](#set) for more details.
|
|
||||||
*
|
|
||||||
* @param int|null $limit maximum number of entries to store in the LRU cache
|
|
||||||
*/
|
|
||||||
public function __construct($limit = null)
|
|
||||||
{
|
|
||||||
$this->limit = $limit;
|
|
||||||
|
|
||||||
// prefer high-resolution timer, available as of PHP 7.3+
|
|
||||||
$this->supportsHighResolution = \function_exists('hrtime');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get($key, $default = null)
|
|
||||||
{
|
|
||||||
// delete key if it is already expired => below will detect this as a cache miss
|
|
||||||
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
|
|
||||||
unset($this->data[$key], $this->expires[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!\array_key_exists($key, $this->data)) {
|
|
||||||
return Promise\resolve($default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove and append to end of array to keep track of LRU info
|
|
||||||
$value = $this->data[$key];
|
|
||||||
unset($this->data[$key]);
|
|
||||||
$this->data[$key] = $value;
|
|
||||||
|
|
||||||
return Promise\resolve($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set($key, $value, $ttl = null)
|
|
||||||
{
|
|
||||||
// unset before setting to ensure this entry will be added to end of array (LRU info)
|
|
||||||
unset($this->data[$key]);
|
|
||||||
$this->data[$key] = $value;
|
|
||||||
|
|
||||||
// sort expiration times if TTL is given (first will expire first)
|
|
||||||
unset($this->expires[$key]);
|
|
||||||
if ($ttl !== null) {
|
|
||||||
$this->expires[$key] = $this->now() + $ttl;
|
|
||||||
\asort($this->expires);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure size limit is not exceeded or remove first entry from array
|
|
||||||
if ($this->limit !== null && \count($this->data) > $this->limit) {
|
|
||||||
// first try to check if there's any expired entry
|
|
||||||
// expiration times are sorted, so we can simply look at the first one
|
|
||||||
\reset($this->expires);
|
|
||||||
$key = \key($this->expires);
|
|
||||||
|
|
||||||
// check to see if the first in the list of expiring keys is already expired
|
|
||||||
// if the first key is not expired, we have to overwrite by using LRU info
|
|
||||||
if ($key === null || $this->now() - $this->expires[$key] < 0) {
|
|
||||||
\reset($this->data);
|
|
||||||
$key = \key($this->data);
|
|
||||||
}
|
|
||||||
unset($this->data[$key], $this->expires[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise\resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete($key)
|
|
||||||
{
|
|
||||||
unset($this->data[$key], $this->expires[$key]);
|
|
||||||
|
|
||||||
return Promise\resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMultiple(array $keys, $default = null)
|
|
||||||
{
|
|
||||||
$values = array();
|
|
||||||
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
$values[$key] = $this->get($key, $default);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise\all($values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMultiple(array $values, $ttl = null)
|
|
||||||
{
|
|
||||||
foreach ($values as $key => $value) {
|
|
||||||
$this->set($key, $value, $ttl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise\resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteMultiple(array $keys)
|
|
||||||
{
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
unset($this->data[$key], $this->expires[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise\resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->data = array();
|
|
||||||
$this->expires = array();
|
|
||||||
|
|
||||||
return Promise\resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function has($key)
|
|
||||||
{
|
|
||||||
// delete key if it is already expired
|
|
||||||
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
|
|
||||||
unset($this->data[$key], $this->expires[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!\array_key_exists($key, $this->data)) {
|
|
||||||
return Promise\resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove and append to end of array to keep track of LRU info
|
|
||||||
$value = $this->data[$key];
|
|
||||||
unset($this->data[$key]);
|
|
||||||
$this->data[$key] = $value;
|
|
||||||
|
|
||||||
return Promise\resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
private function now()
|
|
||||||
{
|
|
||||||
return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
194
vendor/react/cache/src/CacheInterface.php
vendored
194
vendor/react/cache/src/CacheInterface.php
vendored
@@ -1,194 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Cache;
|
|
||||||
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
interface CacheInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Retrieves an item from the cache.
|
|
||||||
*
|
|
||||||
* This method will resolve with the cached value on success or with the
|
|
||||||
* given `$default` value when no item can be found or when an error occurs.
|
|
||||||
* Similarly, an expired cache item (once the time-to-live is expired) is
|
|
||||||
* considered a cache miss.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache
|
|
||||||
* ->get('foo')
|
|
||||||
* ->then('var_dump');
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This example fetches the value of the key `foo` and passes it to the
|
|
||||||
* `var_dump` function. You can use any of the composition provided by
|
|
||||||
* [promises](https://github.com/reactphp/promise).
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $default Default value to return for cache miss or null if not given.
|
|
||||||
* @return PromiseInterface<mixed>
|
|
||||||
*/
|
|
||||||
public function get($key, $default = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores an item in the cache.
|
|
||||||
*
|
|
||||||
* This method will resolve with `true` on success or `false` when an error
|
|
||||||
* occurs. If the cache implementation has to go over the network to store
|
|
||||||
* it, it may take a while.
|
|
||||||
*
|
|
||||||
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
|
||||||
* for this cache item. If this parameter is omitted (or `null`), the item
|
|
||||||
* will stay in the cache for as long as the underlying implementation
|
|
||||||
* supports. Trying to access an expired cache item results in a cache miss,
|
|
||||||
* see also [`get()`](#get).
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache->set('foo', 'bar', 60);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This example eventually sets the value of the key `foo` to `bar`. If it
|
|
||||||
* already exists, it is overridden.
|
|
||||||
*
|
|
||||||
* This interface does not enforce any particular TTL resolution, so special
|
|
||||||
* care may have to be taken if you rely on very high precision with
|
|
||||||
* millisecond accuracy or below. Cache implementations SHOULD work on a
|
|
||||||
* best effort basis and SHOULD provide at least second accuracy unless
|
|
||||||
* otherwise noted. Many existing cache implementations are known to provide
|
|
||||||
* microsecond or millisecond accuracy, but it's generally not recommended
|
|
||||||
* to rely on this high precision.
|
|
||||||
*
|
|
||||||
* This interface suggests that cache implementations SHOULD use a monotonic
|
|
||||||
* time source if available. Given that a monotonic time source is only
|
|
||||||
* available as of PHP 7.3 by default, cache implementations MAY fall back
|
|
||||||
* to using wall-clock time.
|
|
||||||
* While this does not affect many common use cases, this is an important
|
|
||||||
* distinction for programs that rely on a high time precision or on systems
|
|
||||||
* that are subject to discontinuous time adjustments (time jumps).
|
|
||||||
* This means that if you store a cache item with a TTL of 30s and then
|
|
||||||
* adjust your system time forward by 20s, the cache item SHOULD still
|
|
||||||
* expire in 30s.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $value
|
|
||||||
* @param ?float $ttl
|
|
||||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
|
||||||
*/
|
|
||||||
public function set($key, $value, $ttl = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an item from the cache.
|
|
||||||
*
|
|
||||||
* This method will resolve with `true` on success or `false` when an error
|
|
||||||
* occurs. When no item for `$key` is found in the cache, it also resolves
|
|
||||||
* to `true`. If the cache implementation has to go over the network to
|
|
||||||
* delete it, it may take a while.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache->delete('foo');
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This example eventually deletes the key `foo` from the cache. As with
|
|
||||||
* `set()`, this may not happen instantly and a promise is returned to
|
|
||||||
* provide guarantees whether or not the item has been removed from cache.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
|
||||||
*/
|
|
||||||
public function delete($key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves multiple cache items by their unique keys.
|
|
||||||
*
|
|
||||||
* This method will resolve with an array of cached values on success or with the
|
|
||||||
* given `$default` value when an item can not be found or when an error occurs.
|
|
||||||
* Similarly, an expired cache item (once the time-to-live is expired) is
|
|
||||||
* considered a cache miss.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
|
|
||||||
* $name = $values['name'] ?? 'User';
|
|
||||||
* $age = $values['age'] ?? 'n/a';
|
|
||||||
*
|
|
||||||
* echo $name . ' is ' . $age . PHP_EOL;
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This example fetches the cache items for the `name` and `age` keys and
|
|
||||||
* prints some example output. You can use any of the composition provided
|
|
||||||
* by [promises](https://github.com/reactphp/promise).
|
|
||||||
*
|
|
||||||
* @param string[] $keys A list of keys that can obtained in a single operation.
|
|
||||||
* @param mixed $default Default value to return for keys that do not exist.
|
|
||||||
* @return PromiseInterface<array> Returns a promise which resolves to an `array` of cached values
|
|
||||||
*/
|
|
||||||
public function getMultiple(array $keys, $default = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persists a set of key => value pairs in the cache, with an optional TTL.
|
|
||||||
*
|
|
||||||
* This method will resolve with `true` on success or `false` when an error
|
|
||||||
* occurs. If the cache implementation has to go over the network to store
|
|
||||||
* it, it may take a while.
|
|
||||||
*
|
|
||||||
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
|
||||||
* for these cache items. If this parameter is omitted (or `null`), these items
|
|
||||||
* will stay in the cache for as long as the underlying implementation
|
|
||||||
* supports. Trying to access an expired cache items results in a cache miss,
|
|
||||||
* see also [`get()`](#get).
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This example eventually sets the list of values - the key `foo` to 1 value
|
|
||||||
* and the key `bar` to 2. If some of the keys already exist, they are overridden.
|
|
||||||
*
|
|
||||||
* @param array $values A list of key => value pairs for a multiple-set operation.
|
|
||||||
* @param ?float $ttl Optional. The TTL value of this item.
|
|
||||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
|
||||||
*/
|
|
||||||
public function setMultiple(array $values, $ttl = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes multiple cache items in a single operation.
|
|
||||||
*
|
|
||||||
* @param string[] $keys A list of string-based keys to be deleted.
|
|
||||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
|
||||||
*/
|
|
||||||
public function deleteMultiple(array $keys);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wipes clean the entire cache.
|
|
||||||
*
|
|
||||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
|
||||||
*/
|
|
||||||
public function clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether an item is present in the cache.
|
|
||||||
*
|
|
||||||
* This method will resolve with `true` on success or `false` when no item can be found
|
|
||||||
* or when an error occurs. Similarly, an expired cache item (once the time-to-live
|
|
||||||
* is expired) is considered a cache miss.
|
|
||||||
*
|
|
||||||
* ```php
|
|
||||||
* $cache
|
|
||||||
* ->has('foo')
|
|
||||||
* ->then('var_dump');
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This example checks if the value of the key `foo` is set in the cache and passes
|
|
||||||
* the result to the `var_dump` function. You can use any of the composition provided by
|
|
||||||
* [promises](https://github.com/reactphp/promise).
|
|
||||||
*
|
|
||||||
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
|
|
||||||
* and not to be used within your live applications operations for get/set, as this method
|
|
||||||
* is subject to a race condition where your has() will return true and immediately after,
|
|
||||||
* another script can remove it making the state of your app out of date.
|
|
||||||
*
|
|
||||||
* @param string $key The cache item key.
|
|
||||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
|
||||||
*/
|
|
||||||
public function has($key);
|
|
||||||
}
|
|
||||||
452
vendor/react/dns/CHANGELOG.md
vendored
452
vendor/react/dns/CHANGELOG.md
vendored
@@ -1,452 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## 1.13.0 (2024-06-13)
|
|
||||||
|
|
||||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
|
||||||
(#224 by @WyriHaximus)
|
|
||||||
|
|
||||||
## 1.12.0 (2023-11-29)
|
|
||||||
|
|
||||||
* Feature: Full PHP 8.3 compatibility.
|
|
||||||
(#217 by @sergiy-petrov)
|
|
||||||
|
|
||||||
* Update test environment and avoid unhandled promise rejections.
|
|
||||||
(#215, #216 and #218 by @clue)
|
|
||||||
|
|
||||||
## 1.11.0 (2023-06-02)
|
|
||||||
|
|
||||||
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
|
|
||||||
(#213 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite and project setup and report failed assertions.
|
|
||||||
(#210 by @clue, #212 by @WyriHaximus and #209 and #211 by @SimonFrings)
|
|
||||||
|
|
||||||
## 1.10.0 (2022-09-08)
|
|
||||||
|
|
||||||
* Feature: Full support for PHP 8.2 release.
|
|
||||||
(#201 by @clue and #207 by @WyriHaximus)
|
|
||||||
|
|
||||||
* Feature: Optimize forward compatibility with Promise v3, avoid hitting autoloader.
|
|
||||||
(#202 by @clue)
|
|
||||||
|
|
||||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
|
||||||
(#197 by @clue)
|
|
||||||
|
|
||||||
* Fix: Fix invalid references in exception stack trace.
|
|
||||||
(#191 by @clue)
|
|
||||||
|
|
||||||
* Minor documentation improvements.
|
|
||||||
(#195 by @SimonFrings and #203 by @nhedger)
|
|
||||||
|
|
||||||
* Improve test suite, update to use default loop and new reactphp/async package.
|
|
||||||
(#204, #205 and #206 by @clue and #196 by @SimonFrings)
|
|
||||||
|
|
||||||
## 1.9.0 (2021-12-20)
|
|
||||||
|
|
||||||
* Feature: Full support for PHP 8.1 release and prepare PHP 8.2 compatibility
|
|
||||||
by refactoring `Parser` to avoid assigning dynamic properties.
|
|
||||||
(#188 and #186 by @clue and #184 by @SimonFrings)
|
|
||||||
|
|
||||||
* Feature: Avoid dependency on `ext-filter`.
|
|
||||||
(#185 by @clue)
|
|
||||||
|
|
||||||
* Feature / Fix: Skip invalid nameserver entries from `resolv.conf` and ignore IPv6 zone IDs.
|
|
||||||
(#187 by @clue)
|
|
||||||
|
|
||||||
* Feature / Fix: Reduce socket read chunk size for queries over TCP/IP.
|
|
||||||
(#189 by @clue)
|
|
||||||
|
|
||||||
## 1.8.0 (2021-07-11)
|
|
||||||
|
|
||||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
|
||||||
|
|
||||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
|
|
||||||
(#182 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// old (still supported)
|
|
||||||
$factory = new React\Dns\Resolver\Factory();
|
|
||||||
$resolver = $factory->create($config, $loop);
|
|
||||||
|
|
||||||
// new (using default loop)
|
|
||||||
$factory = new React\Dns\Resolver\Factory();
|
|
||||||
$resolver = $factory->create($config);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.7.0 (2021-06-25)
|
|
||||||
|
|
||||||
* Feature: Update DNS `Factory` to accept complete `Config` object.
|
|
||||||
Add new `FallbackExecutor` and use fallback DNS servers when `Config` lists multiple servers.
|
|
||||||
(#179 and #180 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// old (still supported)
|
|
||||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
|
||||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
|
||||||
$resolver = $factory->create($server, $loop);
|
|
||||||
|
|
||||||
// new
|
|
||||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
|
||||||
if (!$config->nameservers) {
|
|
||||||
$config->nameservers[] = '8.8.8.8';
|
|
||||||
}
|
|
||||||
$resolver = $factory->create($config, $loop);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.6.0 (2021-06-21)
|
|
||||||
|
|
||||||
* Feature: Add support for legacy `SPF` record type.
|
|
||||||
(#178 by @akondas and @clue)
|
|
||||||
|
|
||||||
* Fix: Fix integer overflow for TCP/IP chunk size on 32 bit platforms.
|
|
||||||
(#177 by @clue)
|
|
||||||
|
|
||||||
## 1.5.0 (2021-03-05)
|
|
||||||
|
|
||||||
* Feature: Improve error reporting when query fails, include domain and query type and DNS server address where applicable.
|
|
||||||
(#174 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve error handling when sending data to DNS server fails (macOS).
|
|
||||||
(#171 and #172 by @clue)
|
|
||||||
|
|
||||||
* Fix: Improve DNS response parser to limit recursion for compressed labels.
|
|
||||||
(#169 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
|
||||||
(#170 by @SimonFrings)
|
|
||||||
|
|
||||||
## 1.4.0 (2020-09-18)
|
|
||||||
|
|
||||||
* Feature: Support upcoming PHP 8.
|
|
||||||
(#168 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite and update to PHPUnit 9.3.
|
|
||||||
(#164 by @clue, #165 and #166 by @SimonFrings and #167 by @WyriHaximus)
|
|
||||||
|
|
||||||
## 1.3.0 (2020-07-10)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with react/promise v3.
|
|
||||||
(#153 by @WyriHaximus)
|
|
||||||
|
|
||||||
* Feature: Support parsing `OPT` records (EDNS0).
|
|
||||||
(#157 by @clue)
|
|
||||||
|
|
||||||
* Fix: Avoid PHP warnings due to lack of args in exception trace on PHP 7.4.
|
|
||||||
(#160 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
|
||||||
Run tests on PHPUnit 9 and PHP 7.4 and clean up test suite.
|
|
||||||
(#154 by @reedy, #156 by @clue and #163 by @SimonFrings)
|
|
||||||
|
|
||||||
## 1.2.0 (2019-08-15)
|
|
||||||
|
|
||||||
* Feature: Add `TcpTransportExecutor` to send DNS queries over TCP/IP connection,
|
|
||||||
add `SelectiveTransportExecutor` to retry with TCP if UDP is truncated and
|
|
||||||
automatically select transport protocol when no explicit `udp://` or `tcp://` scheme is given in `Factory`.
|
|
||||||
(#145, #146, #147 and #148 by @clue)
|
|
||||||
|
|
||||||
* Feature: Support escaping literal dots and special characters in domain names.
|
|
||||||
(#144 by @clue)
|
|
||||||
|
|
||||||
## 1.1.0 (2019-07-18)
|
|
||||||
|
|
||||||
* Feature: Support parsing `CAA` and `SSHFP` records.
|
|
||||||
(#141 and #142 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add `ResolverInterface` as common interface for `Resolver` class.
|
|
||||||
(#139 by @clue)
|
|
||||||
|
|
||||||
* Fix: Add missing private property definitions and
|
|
||||||
remove unneeded dependency on `react/stream`.
|
|
||||||
(#140 and #143 by @clue)
|
|
||||||
|
|
||||||
## 1.0.0 (2019-07-11)
|
|
||||||
|
|
||||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
|
||||||
We'd like to emphasize that this component is production ready and battle-tested.
|
|
||||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
|
||||||
so you have a rock-solid foundation to build on top of.
|
|
||||||
|
|
||||||
This update involves a number of BC breaks due to dropped support for
|
|
||||||
deprecated functionality and some internal API cleanup. We've tried hard to
|
|
||||||
avoid BC breaks where possible and minimize impact otherwise. We expect that
|
|
||||||
most consumers of this package will actually not be affected by any BC
|
|
||||||
breaks, see below for more details:
|
|
||||||
|
|
||||||
* BC break: Delete all deprecated APIs, use `Query` objects for `Message` questions
|
|
||||||
instead of nested arrays and increase code coverage to 100%.
|
|
||||||
(#130 by @clue)
|
|
||||||
|
|
||||||
* BC break: Move `$nameserver` from `ExecutorInterface` to `UdpTransportExecutor`,
|
|
||||||
remove advanced/internal `UdpTransportExecutor` args for `Parser`/`BinaryDumper` and
|
|
||||||
add API documentation for `ExecutorInterface`.
|
|
||||||
(#135, #137 and #138 by @clue)
|
|
||||||
|
|
||||||
* BC break: Replace `HeaderBag` attributes with simple `Message` properties.
|
|
||||||
(#132 by @clue)
|
|
||||||
|
|
||||||
* BC break: Mark all `Record` attributes as required, add documentation vs `Query`.
|
|
||||||
(#136 by @clue)
|
|
||||||
|
|
||||||
* BC break: Mark all classes as final to discourage inheritance
|
|
||||||
(#134 by @WyriHaximus)
|
|
||||||
|
|
||||||
## 0.4.19 (2019-07-10)
|
|
||||||
|
|
||||||
* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
|
|
||||||
(#133 by @clue)
|
|
||||||
|
|
||||||
## 0.4.18 (2019-09-07)
|
|
||||||
|
|
||||||
* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
|
|
||||||
respect TTL from response records when caching and do not cache truncated responses.
|
|
||||||
(#129 by @clue)
|
|
||||||
|
|
||||||
* Feature: Limit cache size to 256 last responses by default.
|
|
||||||
(#127 by @clue)
|
|
||||||
|
|
||||||
* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
|
|
||||||
(#125 by @clue)
|
|
||||||
|
|
||||||
## 0.4.17 (2019-04-01)
|
|
||||||
|
|
||||||
* Feature: Support parsing `authority` and `additional` records from DNS response.
|
|
||||||
(#123 by @clue)
|
|
||||||
|
|
||||||
* Feature: Support dumping records as part of outgoing binary DNS message.
|
|
||||||
(#124 by @clue)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
|
|
||||||
(#121 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite to add forward compatibility with PHPUnit 7,
|
|
||||||
test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
|
|
||||||
(#122 by @clue)
|
|
||||||
|
|
||||||
## 0.4.16 (2018-11-11)
|
|
||||||
|
|
||||||
* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
|
|
||||||
(#118 by @clue)
|
|
||||||
|
|
||||||
* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
|
|
||||||
malformed record data or malformed compressed domain name labels.
|
|
||||||
(#115 and #117 by @clue)
|
|
||||||
|
|
||||||
* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
|
|
||||||
(#116 by @clue)
|
|
||||||
|
|
||||||
* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
|
|
||||||
(#112 by @clue)
|
|
||||||
|
|
||||||
## 0.4.15 (2018-07-02)
|
|
||||||
|
|
||||||
* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
|
|
||||||
(#110 by @clue and @WyriHaximus)
|
|
||||||
|
|
||||||
```php
|
|
||||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
|
||||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
|
|
||||||
(#104, #105, #106, #107 and #108 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
|
|
||||||
(#104 by @clue)
|
|
||||||
|
|
||||||
* Feature: Improve error messages for failed queries and improve documentation.
|
|
||||||
(#109 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add reverse DNS lookup example.
|
|
||||||
(#111 by @clue)
|
|
||||||
|
|
||||||
## 0.4.14 (2018-06-26)
|
|
||||||
|
|
||||||
* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
|
|
||||||
to avoid cache poisoning attacks and deprecate legacy `Executor`.
|
|
||||||
(#101 and #103 by @clue)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with Cache 0.5
|
|
||||||
(#102 by @clue)
|
|
||||||
|
|
||||||
* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
|
|
||||||
(#99 by @clue)
|
|
||||||
|
|
||||||
## 0.4.13 (2018-02-27)
|
|
||||||
|
|
||||||
* Add `Config::loadSystemConfigBlocking()` to load default system config
|
|
||||||
and support parsing DNS config on all supported platforms
|
|
||||||
(`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
|
|
||||||
(#92, #93, #94 and #95 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
$config = Config::loadSystemConfigBlocking();
|
|
||||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
|
||||||
```
|
|
||||||
|
|
||||||
* Remove unneeded cyclic dependency on react/socket
|
|
||||||
(#96 by @clue)
|
|
||||||
|
|
||||||
## 0.4.12 (2018-01-14)
|
|
||||||
|
|
||||||
* Improve test suite by adding forward compatibility with PHPUnit 6,
|
|
||||||
test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
|
|
||||||
add test group to skip integration tests relying on internet connection
|
|
||||||
and add minor documentation improvements.
|
|
||||||
(#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
|
|
||||||
|
|
||||||
## 0.4.11 (2017-08-25)
|
|
||||||
|
|
||||||
* Feature: Support resolving from default hosts file
|
|
||||||
(#75, #76 and #77 by @clue)
|
|
||||||
|
|
||||||
This means that resolving hosts such as `localhost` will now work as
|
|
||||||
expected across all platforms with no changes required:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$resolver->resolve('localhost')->then(function ($ip) {
|
|
||||||
echo 'IP: ' . $ip;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
The new `HostsExecutor` exists for advanced usage and is otherwise used
|
|
||||||
internally for this feature.
|
|
||||||
|
|
||||||
## 0.4.10 (2017-08-10)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
|
|
||||||
lock minimum dependencies and work around circular dependency for tests
|
|
||||||
(#70 and #71 by @clue)
|
|
||||||
|
|
||||||
* Fix: Work around DNS timeout issues for Windows users
|
|
||||||
(#74 by @clue)
|
|
||||||
|
|
||||||
* Documentation and examples for advanced usage
|
|
||||||
(#66 by @WyriHaximus)
|
|
||||||
|
|
||||||
* Remove broken TCP code, do not retry with invalid TCP query
|
|
||||||
(#73 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
|
|
||||||
lock Travis distro so new defaults will not break the build and
|
|
||||||
fix failing tests for PHP 7.1
|
|
||||||
(#68 by @WyriHaximus and #69 and #72 by @clue)
|
|
||||||
|
|
||||||
## 0.4.9 (2017-05-01)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
|
||||||
(#61 by @clue)
|
|
||||||
|
|
||||||
## 0.4.8 (2017-04-16)
|
|
||||||
|
|
||||||
* Feature: Add support for the AAAA record type to the protocol parser
|
|
||||||
(#58 by @othillo)
|
|
||||||
|
|
||||||
* Feature: Add support for the PTR record type to the protocol parser
|
|
||||||
(#59 by @othillo)
|
|
||||||
|
|
||||||
## 0.4.7 (2017-03-31)
|
|
||||||
|
|
||||||
* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
|
|
||||||
(#57 by @clue)
|
|
||||||
|
|
||||||
## 0.4.6 (2017-03-11)
|
|
||||||
|
|
||||||
* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
|
|
||||||
with Stream v0.5 and upcoming v0.6
|
|
||||||
(#53 by @clue)
|
|
||||||
|
|
||||||
* Improve test suite by adding PHPUnit to `require-dev`
|
|
||||||
(#54 by @clue)
|
|
||||||
|
|
||||||
## 0.4.5 (2017-03-02)
|
|
||||||
|
|
||||||
* Fix: Ensure we ignore the case of the answer
|
|
||||||
(#51 by @WyriHaximus)
|
|
||||||
|
|
||||||
* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
|
|
||||||
code re-use for upcoming versions.
|
|
||||||
(#48 and #49 by @clue)
|
|
||||||
|
|
||||||
## 0.4.4 (2017-02-13)
|
|
||||||
|
|
||||||
* Fix: Fix handling connection and stream errors
|
|
||||||
(#45 by @clue)
|
|
||||||
|
|
||||||
* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
|
|
||||||
(#46 and #47 by @clue)
|
|
||||||
|
|
||||||
## 0.4.3 (2016-07-31)
|
|
||||||
|
|
||||||
* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
|
|
||||||
|
|
||||||
```php
|
|
||||||
$factory = new React\Dns\Resolver\Factory();
|
|
||||||
|
|
||||||
$cache = new MyCustomCacheInstance();
|
|
||||||
$resolver = $factory->createCached('8.8.8.8', $loop, $cache);
|
|
||||||
```
|
|
||||||
|
|
||||||
* Feature: Support Promise cancellation (#35 by @clue)
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = $resolver->resolve('reactphp.org');
|
|
||||||
|
|
||||||
$promise->cancel();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 0.4.2 (2016-02-24)
|
|
||||||
|
|
||||||
* Repository maintenance, split off from main repo, improve test suite and documentation
|
|
||||||
* First class support for PHP7 and HHVM (#34 by @clue)
|
|
||||||
* Adjust compatibility to 5.3 (#30 by @clue)
|
|
||||||
|
|
||||||
## 0.4.1 (2014-04-13)
|
|
||||||
|
|
||||||
* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
|
|
||||||
|
|
||||||
## 0.4.0 (2014-02-02)
|
|
||||||
|
|
||||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
|
||||||
* BC break: Update to React/Promise 2.0
|
|
||||||
* Bug fix: Properly resolve CNAME aliases
|
|
||||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
|
||||||
* Bump React dependencies to v0.4
|
|
||||||
|
|
||||||
## 0.3.2 (2013-05-10)
|
|
||||||
|
|
||||||
* Feature: Support default port for IPv6 addresses (@clue)
|
|
||||||
|
|
||||||
## 0.3.0 (2013-04-14)
|
|
||||||
|
|
||||||
* Bump React dependencies to v0.3
|
|
||||||
|
|
||||||
## 0.2.6 (2012-12-26)
|
|
||||||
|
|
||||||
* Feature: New cache component, used by DNS
|
|
||||||
|
|
||||||
## 0.2.5 (2012-11-26)
|
|
||||||
|
|
||||||
* Version bump
|
|
||||||
|
|
||||||
## 0.2.4 (2012-11-18)
|
|
||||||
|
|
||||||
* Feature: Change to promise-based API (@jsor)
|
|
||||||
|
|
||||||
## 0.2.3 (2012-11-14)
|
|
||||||
|
|
||||||
* Version bump
|
|
||||||
|
|
||||||
## 0.2.2 (2012-10-28)
|
|
||||||
|
|
||||||
* Feature: DNS executor timeout handling (@arnaud-lb)
|
|
||||||
* Feature: DNS retry executor (@arnaud-lb)
|
|
||||||
|
|
||||||
## 0.2.1 (2012-10-14)
|
|
||||||
|
|
||||||
* Minor adjustments to DNS parser
|
|
||||||
|
|
||||||
## 0.2.0 (2012-09-10)
|
|
||||||
|
|
||||||
* Feature: DNS resolver
|
|
||||||
21
vendor/react/dns/LICENSE
vendored
21
vendor/react/dns/LICENSE
vendored
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
|
||||||
to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
453
vendor/react/dns/README.md
vendored
453
vendor/react/dns/README.md
vendored
@@ -1,453 +0,0 @@
|
|||||||
# DNS
|
|
||||||
|
|
||||||
[](https://github.com/reactphp/dns/actions)
|
|
||||||
[](https://packagist.org/packages/react/dns)
|
|
||||||
|
|
||||||
Async DNS resolver for [ReactPHP](https://reactphp.org/).
|
|
||||||
|
|
||||||
The main point of the DNS component is to provide async DNS resolution.
|
|
||||||
However, it is really a toolkit for working with DNS messages, and could
|
|
||||||
easily be used to create a DNS server.
|
|
||||||
|
|
||||||
**Table of contents**
|
|
||||||
|
|
||||||
* [Basic usage](#basic-usage)
|
|
||||||
* [Caching](#caching)
|
|
||||||
* [Custom cache adapter](#custom-cache-adapter)
|
|
||||||
* [ResolverInterface](#resolverinterface)
|
|
||||||
* [resolve()](#resolve)
|
|
||||||
* [resolveAll()](#resolveall)
|
|
||||||
* [Advanced usage](#advanced-usage)
|
|
||||||
* [UdpTransportExecutor](#udptransportexecutor)
|
|
||||||
* [TcpTransportExecutor](#tcptransportexecutor)
|
|
||||||
* [SelectiveTransportExecutor](#selectivetransportexecutor)
|
|
||||||
* [HostsFileExecutor](#hostsfileexecutor)
|
|
||||||
* [Install](#install)
|
|
||||||
* [Tests](#tests)
|
|
||||||
* [License](#license)
|
|
||||||
* [References](#references)
|
|
||||||
|
|
||||||
## Basic usage
|
|
||||||
|
|
||||||
The most basic usage is to just create a resolver through the resolver
|
|
||||||
factory. All you need to give it is a nameserver, then you can start resolving
|
|
||||||
names, baby!
|
|
||||||
|
|
||||||
```php
|
|
||||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
|
||||||
if (!$config->nameservers) {
|
|
||||||
$config->nameservers[] = '8.8.8.8';
|
|
||||||
}
|
|
||||||
|
|
||||||
$factory = new React\Dns\Resolver\Factory();
|
|
||||||
$dns = $factory->create($config);
|
|
||||||
|
|
||||||
$dns->resolve('igor.io')->then(function ($ip) {
|
|
||||||
echo "Host: $ip\n";
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [first example](examples).
|
|
||||||
|
|
||||||
The `Config` class can be used to load the system default config. This is an
|
|
||||||
operation that may access the filesystem and block. Ideally, this method should
|
|
||||||
thus be executed only once before the loop starts and not repeatedly while it is
|
|
||||||
running.
|
|
||||||
Note that this class may return an *empty* configuration if the system config
|
|
||||||
can not be loaded. As such, you'll likely want to apply a default nameserver
|
|
||||||
as above if none can be found.
|
|
||||||
|
|
||||||
> Note that the factory loads the hosts file from the filesystem once when
|
|
||||||
creating the resolver instance.
|
|
||||||
Ideally, this method should thus be executed only once before the loop starts
|
|
||||||
and not repeatedly while it is running.
|
|
||||||
|
|
||||||
But there's more.
|
|
||||||
|
|
||||||
## Caching
|
|
||||||
|
|
||||||
You can cache results by configuring the resolver to use a `CachedExecutor`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
|
||||||
if (!$config->nameservers) {
|
|
||||||
$config->nameservers[] = '8.8.8.8';
|
|
||||||
}
|
|
||||||
|
|
||||||
$factory = new React\Dns\Resolver\Factory();
|
|
||||||
$dns = $factory->createCached($config);
|
|
||||||
|
|
||||||
$dns->resolve('igor.io')->then(function ($ip) {
|
|
||||||
echo "Host: $ip\n";
|
|
||||||
});
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
$dns->resolve('igor.io')->then(function ($ip) {
|
|
||||||
echo "Host: $ip\n";
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
If the first call returns before the second, only one query will be executed.
|
|
||||||
The second result will be served from an in memory cache.
|
|
||||||
This is particularly useful for long running scripts where the same hostnames
|
|
||||||
have to be looked up multiple times.
|
|
||||||
|
|
||||||
See also the [third example](examples).
|
|
||||||
|
|
||||||
### Custom cache adapter
|
|
||||||
|
|
||||||
By default, the above will use an in memory cache.
|
|
||||||
|
|
||||||
You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache = new React\Cache\ArrayCache();
|
|
||||||
$factory = new React\Dns\Resolver\Factory();
|
|
||||||
$dns = $factory->createCached('8.8.8.8', null, $cache);
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
|
|
||||||
|
|
||||||
## ResolverInterface
|
|
||||||
|
|
||||||
<a id="resolver"><!-- legacy reference --></a>
|
|
||||||
|
|
||||||
### resolve()
|
|
||||||
|
|
||||||
The `resolve(string $domain): PromiseInterface<string>` method can be used to
|
|
||||||
resolve the given $domain name to a single IPv4 address (type `A` query).
|
|
||||||
|
|
||||||
```php
|
|
||||||
$resolver->resolve('reactphp.org')->then(function ($ip) {
|
|
||||||
echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
This is one of the main methods in this package. It sends a DNS query
|
|
||||||
for the given $domain name to your DNS server and returns a single IP
|
|
||||||
address on success.
|
|
||||||
|
|
||||||
If the DNS server sends a DNS response message that contains more than
|
|
||||||
one IP address for this query, it will randomly pick one of the IP
|
|
||||||
addresses from the response. If you want the full list of IP addresses
|
|
||||||
or want to send a different type of query, you should use the
|
|
||||||
[`resolveAll()`](#resolveall) method instead.
|
|
||||||
|
|
||||||
If the DNS server sends a DNS response message that indicates an error
|
|
||||||
code, this method will reject with a `RecordNotFoundException`. Its
|
|
||||||
message and code can be used to check for the response code.
|
|
||||||
|
|
||||||
If the DNS communication fails and the server does not respond with a
|
|
||||||
valid response message, this message will reject with an `Exception`.
|
|
||||||
|
|
||||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = $resolver->resolve('reactphp.org');
|
|
||||||
|
|
||||||
$promise->cancel();
|
|
||||||
```
|
|
||||||
|
|
||||||
### resolveAll()
|
|
||||||
|
|
||||||
The `resolveAll(string $host, int $type): PromiseInterface<array>` method can be used to
|
|
||||||
resolve all record values for the given $domain name and query $type.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
|
|
||||||
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
|
||||||
});
|
|
||||||
|
|
||||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
|
||||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
This is one of the main methods in this package. It sends a DNS query
|
|
||||||
for the given $domain name to your DNS server and returns a list with all
|
|
||||||
record values on success.
|
|
||||||
|
|
||||||
If the DNS server sends a DNS response message that contains one or more
|
|
||||||
records for this query, it will return a list with all record values
|
|
||||||
from the response. You can use the `Message::TYPE_*` constants to control
|
|
||||||
which type of query will be sent. Note that this method always returns a
|
|
||||||
list of record values, but each record value type depends on the query
|
|
||||||
type. For example, it returns the IPv4 addresses for type `A` queries,
|
|
||||||
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
|
|
||||||
`CNAME` and `PTR` queries and structured data for other queries. See also
|
|
||||||
the `Record` documentation for more details.
|
|
||||||
|
|
||||||
If the DNS server sends a DNS response message that indicates an error
|
|
||||||
code, this method will reject with a `RecordNotFoundException`. Its
|
|
||||||
message and code can be used to check for the response code.
|
|
||||||
|
|
||||||
If the DNS communication fails and the server does not respond with a
|
|
||||||
valid response message, this message will reject with an `Exception`.
|
|
||||||
|
|
||||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
|
|
||||||
|
|
||||||
$promise->cancel();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### UdpTransportExecutor
|
|
||||||
|
|
||||||
The `UdpTransportExecutor` can be used to
|
|
||||||
send DNS queries over a UDP transport.
|
|
||||||
|
|
||||||
This is the main class that sends a DNS query to your DNS server and is used
|
|
||||||
internally by the `Resolver` for the actual message transport.
|
|
||||||
|
|
||||||
For more advanced usages one can utilize this class directly.
|
|
||||||
The following example looks up the `IPv6` address for `igor.io`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
|
||||||
|
|
||||||
$executor->query(
|
|
||||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
|
||||||
)->then(function (Message $message) {
|
|
||||||
foreach ($message->answers as $answer) {
|
|
||||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
|
||||||
}
|
|
||||||
}, 'printf');
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [fourth example](examples).
|
|
||||||
|
|
||||||
Note that this executor does not implement a timeout, so you will very likely
|
|
||||||
want to use this in combination with a `TimeoutExecutor` like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new TimeoutExecutor(
|
|
||||||
new UdpTransportExecutor($nameserver),
|
|
||||||
3.0
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Also note that this executor uses an unreliable UDP transport and that it
|
|
||||||
does not implement any retry logic, so you will likely want to use this in
|
|
||||||
combination with a `RetryExecutor` like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new RetryExecutor(
|
|
||||||
new TimeoutExecutor(
|
|
||||||
new UdpTransportExecutor($nameserver),
|
|
||||||
3.0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this executor is entirely async and as such allows you to execute
|
|
||||||
any number of queries concurrently. You should probably limit the number of
|
|
||||||
concurrent queries in your application or you're very likely going to face
|
|
||||||
rate limitations and bans on the resolver end. For many common applications,
|
|
||||||
you may want to avoid sending the same query multiple times when the first
|
|
||||||
one is still pending, so you will likely want to use this in combination with
|
|
||||||
a `CoopExecutor` like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new CoopExecutor(
|
|
||||||
new RetryExecutor(
|
|
||||||
new TimeoutExecutor(
|
|
||||||
new UdpTransportExecutor($nameserver),
|
|
||||||
3.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> Internally, this class uses PHP's UDP sockets and does not take advantage
|
|
||||||
of [react/datagram](https://github.com/reactphp/datagram) purely for
|
|
||||||
organizational reasons to avoid a cyclic dependency between the two
|
|
||||||
packages. Higher-level components should take advantage of the Datagram
|
|
||||||
component instead of reimplementing this socket logic from scratch.
|
|
||||||
|
|
||||||
### TcpTransportExecutor
|
|
||||||
|
|
||||||
The `TcpTransportExecutor` class can be used to
|
|
||||||
send DNS queries over a TCP/IP stream transport.
|
|
||||||
|
|
||||||
This is one of the main classes that send a DNS query to your DNS server.
|
|
||||||
|
|
||||||
For more advanced usages one can utilize this class directly.
|
|
||||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new TcpTransportExecutor('8.8.8.8:53');
|
|
||||||
|
|
||||||
$executor->query(
|
|
||||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
|
||||||
)->then(function (Message $message) {
|
|
||||||
foreach ($message->answers as $answer) {
|
|
||||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
|
||||||
}
|
|
||||||
}, 'printf');
|
|
||||||
```
|
|
||||||
|
|
||||||
See also [example #92](examples).
|
|
||||||
|
|
||||||
Note that this executor does not implement a timeout, so you will very likely
|
|
||||||
want to use this in combination with a `TimeoutExecutor` like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new TimeoutExecutor(
|
|
||||||
new TcpTransportExecutor($nameserver),
|
|
||||||
3.0
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
|
|
||||||
transport, so you do not necessarily have to implement any retry logic.
|
|
||||||
|
|
||||||
Note that this executor is entirely async and as such allows you to execute
|
|
||||||
queries concurrently. The first query will establish a TCP/IP socket
|
|
||||||
connection to the DNS server which will be kept open for a short period.
|
|
||||||
Additional queries will automatically reuse this existing socket connection
|
|
||||||
to the DNS server, will pipeline multiple requests over this single
|
|
||||||
connection and will keep an idle connection open for a short period. The
|
|
||||||
initial TCP/IP connection overhead may incur a slight delay if you only send
|
|
||||||
occasional queries – when sending a larger number of concurrent queries over
|
|
||||||
an existing connection, it becomes increasingly more efficient and avoids
|
|
||||||
creating many concurrent sockets like the UDP-based executor. You may still
|
|
||||||
want to limit the number of (concurrent) queries in your application or you
|
|
||||||
may be facing rate limitations and bans on the resolver end. For many common
|
|
||||||
applications, you may want to avoid sending the same query multiple times
|
|
||||||
when the first one is still pending, so you will likely want to use this in
|
|
||||||
combination with a `CoopExecutor` like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new CoopExecutor(
|
|
||||||
new TimeoutExecutor(
|
|
||||||
new TcpTransportExecutor($nameserver),
|
|
||||||
3.0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> Internally, this class uses PHP's TCP/IP sockets and does not take advantage
|
|
||||||
of [react/socket](https://github.com/reactphp/socket) purely for
|
|
||||||
organizational reasons to avoid a cyclic dependency between the two
|
|
||||||
packages. Higher-level components should take advantage of the Socket
|
|
||||||
component instead of reimplementing this socket logic from scratch.
|
|
||||||
|
|
||||||
### SelectiveTransportExecutor
|
|
||||||
|
|
||||||
The `SelectiveTransportExecutor` class can be used to
|
|
||||||
Send DNS queries over a UDP or TCP/IP stream transport.
|
|
||||||
|
|
||||||
This class will automatically choose the correct transport protocol to send
|
|
||||||
a DNS query to your DNS server. It will always try to send it over the more
|
|
||||||
efficient UDP transport first. If this query yields a size related issue
|
|
||||||
(truncated messages), it will retry over a streaming TCP/IP transport.
|
|
||||||
|
|
||||||
For more advanced usages one can utilize this class directly.
|
|
||||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
|
|
||||||
|
|
||||||
$executor->query(
|
|
||||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
|
||||||
)->then(function (Message $message) {
|
|
||||||
foreach ($message->answers as $answer) {
|
|
||||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
|
||||||
}
|
|
||||||
}, 'printf');
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this executor only implements the logic to select the correct
|
|
||||||
transport for the given DNS query. Implementing the correct transport logic,
|
|
||||||
implementing timeouts and any retry logic is left up to the given executors,
|
|
||||||
see also [`UdpTransportExecutor`](#udptransportexecutor) and
|
|
||||||
[`TcpTransportExecutor`](#tcptransportexecutor) for more details.
|
|
||||||
|
|
||||||
Note that this executor is entirely async and as such allows you to execute
|
|
||||||
any number of queries concurrently. You should probably limit the number of
|
|
||||||
concurrent queries in your application or you're very likely going to face
|
|
||||||
rate limitations and bans on the resolver end. For many common applications,
|
|
||||||
you may want to avoid sending the same query multiple times when the first
|
|
||||||
one is still pending, so you will likely want to use this in combination with
|
|
||||||
a `CoopExecutor` like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$executor = new CoopExecutor(
|
|
||||||
new SelectiveTransportExecutor(
|
|
||||||
$datagramExecutor,
|
|
||||||
$streamExecutor
|
|
||||||
)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### HostsFileExecutor
|
|
||||||
|
|
||||||
Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
|
|
||||||
If you also want to take entries from your hosts file into account, you may
|
|
||||||
use this code:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
|
|
||||||
|
|
||||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
|
||||||
$executor = new HostsFileExecutor($hosts, $executor);
|
|
||||||
|
|
||||||
$executor->query(
|
|
||||||
new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
|
||||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
|
||||||
|
|
||||||
This project follows [SemVer](https://semver.org/).
|
|
||||||
This will install the latest supported version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer require react/dns:^1.13
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
|
||||||
|
|
||||||
This project aims to run on any platform and thus does not require any PHP
|
|
||||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
|
||||||
HHVM.
|
|
||||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
To run the test suite, you first need to clone this repo and then install all
|
|
||||||
dependencies [through Composer](https://getcomposer.org/):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer install
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the test suite, go to the project root and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vendor/bin/phpunit
|
|
||||||
```
|
|
||||||
|
|
||||||
The test suite also contains a number of functional integration tests that rely
|
|
||||||
on a stable internet connection.
|
|
||||||
If you do not want to run these, they can simply be skipped like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vendor/bin/phpunit --exclude-group internet
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT, see [LICENSE file](LICENSE).
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
|
|
||||||
* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
|
|
||||||
49
vendor/react/dns/composer.json
vendored
49
vendor/react/dns/composer.json
vendored
@@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "react/dns",
|
|
||||||
"description": "Async DNS resolver for ReactPHP",
|
|
||||||
"keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Christian Lück",
|
|
||||||
"homepage": "https://clue.engineering/",
|
|
||||||
"email": "christian@clue.engineering"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cees-Jan Kiewiet",
|
|
||||||
"homepage": "https://wyrihaximus.net/",
|
|
||||||
"email": "reactphp@ceesjankiewiet.nl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jan Sorgalla",
|
|
||||||
"homepage": "https://sorgalla.com/",
|
|
||||||
"email": "jsorgalla@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Chris Boden",
|
|
||||||
"homepage": "https://cboden.dev/",
|
|
||||||
"email": "cboden@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.3.0",
|
|
||||||
"react/cache": "^1.0 || ^0.6 || ^0.5",
|
|
||||||
"react/event-loop": "^1.2",
|
|
||||||
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
|
||||||
"react/async": "^4.3 || ^3 || ^2",
|
|
||||||
"react/promise-timer": "^1.11"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"React\\Dns\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload-dev": {
|
|
||||||
"psr-4": {
|
|
||||||
"React\\Tests\\Dns\\": "tests/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
vendor/react/dns/src/BadServerException.php
vendored
7
vendor/react/dns/src/BadServerException.php
vendored
@@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace React\Dns;
|
|
||||||
|
|
||||||
final class BadServerException extends \Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user