mirror of
https://github.com/linuxserver/Heimdall.git
synced 2026-08-07 07:16:13 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 238452c104 | |||
| 91f7a2ec8d | |||
| 62823aef63 | |||
| 2ceec61e17 | |||
| 8fc5f3ccbf | |||
| 6a45d44f69 | |||
| 327af60d1c | |||
| a38ecd4864 | |||
| 1f7432fa26 | |||
| 55bda9daf0 | |||
| 667dd4c129 | |||
| d16b7f60f3 | |||
| 57fa9f26fb | |||
| bdf9160fdc | |||
| 93a321ff5b |
@@ -0,0 +1,97 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
# Stage 1 of the release flow: opens a version-bump PR against 2.x.
|
||||||
|
# When that PR is merged, tag-release.yml (stage 2) creates the tag and
|
||||||
|
# the GitHub release automatically.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
bump:
|
||||||
|
description: 'Version bump type'
|
||||||
|
required: true
|
||||||
|
default: 'patch'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- patch
|
||||||
|
- minor
|
||||||
|
- major
|
||||||
|
version:
|
||||||
|
description: 'Explicit version (e.g. 2.9.0) — overrides bump type'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-pr:
|
||||||
|
name: Open version bump PR
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: 2.x
|
||||||
|
fetch-depth: 0
|
||||||
|
# Optional: set a RELEASE_TOKEN repo secret (fine-grained PAT with
|
||||||
|
# contents + pull-requests write) so the bump PR triggers CI checks.
|
||||||
|
# PRs created with the default github.token do not trigger workflows.
|
||||||
|
token: ${{ secrets.RELEASE_TOKEN || github.token }}
|
||||||
|
|
||||||
|
- name: Determine new version
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
EXPLICIT_VERSION: ${{ inputs.version }}
|
||||||
|
BUMP: ${{ inputs.bump }}
|
||||||
|
run: |
|
||||||
|
current=$(sed -nE "s/^[[:space:]]*'version' => '([0-9]+\.[0-9]+\.[0-9]+)',/\1/p" config/app.php)
|
||||||
|
if [ -z "$current" ]; then
|
||||||
|
echo "::error::Could not read current version from config/app.php"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -n "$EXPLICIT_VERSION" ]; then
|
||||||
|
new="$EXPLICIT_VERSION"
|
||||||
|
if ! echo "$new" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
echo "::error::Invalid version '$new' — expected X.Y.Z"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
IFS=. read -r major minor patch <<< "$current"
|
||||||
|
case "$BUMP" in
|
||||||
|
major) new="$((major + 1)).0.0" ;;
|
||||||
|
minor) new="$major.$((minor + 1)).0" ;;
|
||||||
|
patch) new="$major.$minor.$((patch + 1))" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if git rev-parse -q --verify "refs/tags/v$new" > /dev/null; then
|
||||||
|
echo "::error::Tag v$new already exists"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Bumping $current -> $new"
|
||||||
|
echo "new=$new" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Push bump commit to release branch
|
||||||
|
env:
|
||||||
|
NEW_VERSION: ${{ steps.version.outputs.new }}
|
||||||
|
run: |
|
||||||
|
sed -i -E "s/^([[:space:]]*'version' => ')[0-9.]+(',)/\1$NEW_VERSION\2/" config/app.php
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git commit -am "Bump version to $NEW_VERSION"
|
||||||
|
git push --force origin "HEAD:refs/heads/release/v$NEW_VERSION"
|
||||||
|
|
||||||
|
- name: Open pull request
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
|
||||||
|
NEW_VERSION: ${{ steps.version.outputs.new }}
|
||||||
|
run: |
|
||||||
|
existing=$(gh pr list --head "release/v$NEW_VERSION" --base 2.x --state open --json number -q '.[0].number')
|
||||||
|
if [ -n "$existing" ]; then
|
||||||
|
echo "PR #$existing is already open for release/v$NEW_VERSION"
|
||||||
|
else
|
||||||
|
gh pr create \
|
||||||
|
--base 2.x \
|
||||||
|
--head "release/v$NEW_VERSION" \
|
||||||
|
--title "Bump version to $NEW_VERSION" \
|
||||||
|
--body "Automated version bump. Merging this PR will tag v$NEW_VERSION and publish the GitHub release. Merge it last, once everything for the release is on 2.x."
|
||||||
|
fi
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: Tag and release
|
||||||
|
|
||||||
|
# Stage 2 of the release flow: whenever the version in config/app.php changes
|
||||||
|
# on 2.x (normally by merging the PR opened by release.yml, but a hand-made
|
||||||
|
# bump PR works too), create the matching tag and GitHub release.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 2.x
|
||||||
|
paths:
|
||||||
|
- config/app.php
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tag-release:
|
||||||
|
name: Tag and publish release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Create release if version is untagged
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
version=$(sed -nE "s/^[[:space:]]*'version' => '([0-9]+\.[0-9]+\.[0-9]+)',/\1/p" config/app.php)
|
||||||
|
if [ -z "$version" ]; then
|
||||||
|
echo "::error::Could not read version from config/app.php"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if git rev-parse -q --verify "refs/tags/v$version" > /dev/null; then
|
||||||
|
echo "Tag v$version already exists — nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
gh release create "v$version" \
|
||||||
|
--target "$GITHUB_SHA" \
|
||||||
|
--title "v$version" \
|
||||||
|
--generate-notes
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: Tag version check
|
||||||
|
|
||||||
|
# Safety net for manually pushed tags: fails if the tag doesn't match the
|
||||||
|
# version in config/app.php. Tags created by tag-release.yml use GITHUB_TOKEN
|
||||||
|
# and therefore don't trigger this (they always match anyway).
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Tag matches config/app.php
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Compare tag with app version
|
||||||
|
run: |
|
||||||
|
tag="${GITHUB_REF_NAME#v}"
|
||||||
|
version=$(sed -nE "s/^[[:space:]]*'version' => '([0-9]+\.[0-9]+\.[0-9]+)',/\1/p" config/app.php)
|
||||||
|
if [ "$tag" != "$version" ]; then
|
||||||
|
echo "::error::Tag v$tag does not match config/app.php version $version — bump the version (or use the Release workflow, which does it for you)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Tag v$tag matches config/app.php"
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Phrity\Net\Context;
|
||||||
|
use WebSocket\Client;
|
||||||
|
use WebSocket\Exception\Exception as WebSocketException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TrueNAS JSON-RPC 2.0 WebSocket Client
|
||||||
|
*
|
||||||
|
* Handles WebSocket communication with TrueNAS using the JSON-RPC 2.0 protocol.
|
||||||
|
* Required for TrueNAS 25.04+ as the REST API is deprecated.
|
||||||
|
*
|
||||||
|
* @see https://api.truenas.com/v25.10/jsonrpc.html
|
||||||
|
*/
|
||||||
|
class TrueNASWebSocketClient
|
||||||
|
{
|
||||||
|
private ?Client $client = null;
|
||||||
|
private string $url;
|
||||||
|
private string $apiKey;
|
||||||
|
private bool $ignoreTls;
|
||||||
|
private bool $authenticated = false;
|
||||||
|
private int $requestId = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new TrueNAS WebSocket client instance.
|
||||||
|
*
|
||||||
|
* @param string $baseUrl The base URL of the TrueNAS instance (e.g., https://truenas.local)
|
||||||
|
* @param string $apiKey The API key for authentication
|
||||||
|
* @param bool $ignoreTls Whether to skip TLS certificate verification
|
||||||
|
*/
|
||||||
|
public function __construct(string $baseUrl, string $apiKey, bool $ignoreTls = false)
|
||||||
|
{
|
||||||
|
$baseUrl = rtrim($baseUrl, '/');
|
||||||
|
$scheme = parse_url($baseUrl, PHP_URL_SCHEME);
|
||||||
|
$host = parse_url($baseUrl, PHP_URL_HOST);
|
||||||
|
$port = parse_url($baseUrl, PHP_URL_PORT);
|
||||||
|
|
||||||
|
$wsScheme = ($scheme === 'https') ? 'wss' : 'ws';
|
||||||
|
$portPart = $port ? ':' . $port : '';
|
||||||
|
|
||||||
|
$this->url = "{$wsScheme}://{$host}{$portPart}/api/current";
|
||||||
|
$this->apiKey = $apiKey;
|
||||||
|
$this->ignoreTls = $ignoreTls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the TrueNAS WebSocket API and authenticate.
|
||||||
|
*
|
||||||
|
* @return bool True if connection and authentication succeeded
|
||||||
|
* @throws \Exception If connection or authentication fails
|
||||||
|
*/
|
||||||
|
public function connect(): bool
|
||||||
|
{
|
||||||
|
if ($this->client !== null && $this->authenticated) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build SSL options - always force HTTP/1.1 via ALPN for WebSocket compatibility
|
||||||
|
// TrueNAS nginx defaults to HTTP/2 which doesn't support WebSocket upgrade
|
||||||
|
$sslOptions = [
|
||||||
|
'alpn_protocols' => 'http/1.1',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->ignoreTls) {
|
||||||
|
$sslOptions['verify_peer'] = false;
|
||||||
|
$sslOptions['verify_peer_name'] = false;
|
||||||
|
$sslOptions['allow_self_signed'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create context using phrity/net-stream Context class (required by phrity/websocket v3.x)
|
||||||
|
$streamContext = stream_context_create(['ssl' => $sslOptions]);
|
||||||
|
$context = new Context($streamContext);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->client = new Client($this->url);
|
||||||
|
$this->client->setTimeout(15);
|
||||||
|
$this->client->setContext($context);
|
||||||
|
|
||||||
|
$authResult = $this->call('auth.login_with_api_key', [$this->apiKey]);
|
||||||
|
|
||||||
|
if ($authResult === true) {
|
||||||
|
$this->authenticated = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Exception('Authentication failed: Invalid API key');
|
||||||
|
} catch (WebSocketException $e) {
|
||||||
|
Log::error('TrueNAS WebSocket connection failed: ' . $e->getMessage());
|
||||||
|
$this->disconnect();
|
||||||
|
throw new \Exception('WebSocket connection failed: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a JSON-RPC 2.0 call to the TrueNAS API.
|
||||||
|
*
|
||||||
|
* @param string $method The JSON-RPC method name (e.g., 'system.info')
|
||||||
|
* @param array $params Optional parameters for the method
|
||||||
|
* @return mixed The result from the API call
|
||||||
|
* @throws \Exception If the call fails or returns an error
|
||||||
|
*/
|
||||||
|
public function call(string $method, array $params = [])
|
||||||
|
{
|
||||||
|
if ($this->client === null) {
|
||||||
|
throw new \Exception('WebSocket client not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = [
|
||||||
|
'jsonrpc' => '2.0',
|
||||||
|
'method' => $method,
|
||||||
|
'id' => $this->requestId++,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
$request['params'] = $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->client->text(json_encode($request));
|
||||||
|
$response = $this->client->receive();
|
||||||
|
$decoded = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
|
if (isset($decoded['error'])) {
|
||||||
|
$errorMsg = $decoded['error']['message'] ?? 'Unknown error';
|
||||||
|
$errorCode = $decoded['error']['code'] ?? 0;
|
||||||
|
throw new \Exception("API error ({$errorCode}): {$errorMsg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decoded['result'] ?? null;
|
||||||
|
} catch (WebSocketException $e) {
|
||||||
|
Log::error('TrueNAS WebSocket call failed: ' . $e->getMessage());
|
||||||
|
throw new \Exception('WebSocket call failed: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the WebSocket connection.
|
||||||
|
*/
|
||||||
|
public function disconnect(): void
|
||||||
|
{
|
||||||
|
if ($this->client !== null) {
|
||||||
|
try {
|
||||||
|
$this->client->close();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::debug('Error closing WebSocket: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
$this->client = null;
|
||||||
|
$this->authenticated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the client is connected and authenticated.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isConnected(): bool
|
||||||
|
{
|
||||||
|
return $this->client !== null && $this->authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the connection by calling core.ping.
|
||||||
|
*
|
||||||
|
* @return bool True if the ping succeeds
|
||||||
|
*/
|
||||||
|
public function ping(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$result = $this->call('core.ping');
|
||||||
|
return $result === 'pong';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up on destruction.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,11 @@ abstract class SupportedApps
|
|||||||
'connect_timeout' => 15,
|
'connect_timeout' => 15,
|
||||||
] : $overridevars;
|
] : $overridevars;
|
||||||
|
|
||||||
|
// Check global setting to skip TLS verification (useful for self-signed certificates)
|
||||||
|
if (Setting::fetch('skip_tls_verification')) {
|
||||||
|
$vars['verify'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
$client = new Client($vars);
|
$client = new Client($vars);
|
||||||
|
|
||||||
$method = ($overridemethod === null || $overridemethod === false) ? $this->method : $overridemethod;
|
$method = ($overridemethod === null || $overridemethod === false) ? $this->method : $overridemethod;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"laravel/ui": "^4.4",
|
"laravel/ui": "^4.4",
|
||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
"league/flysystem-aws-s3-v3": "^3.0",
|
||||||
"nunomaduro/collision": "^8.0",
|
"nunomaduro/collision": "^8.0",
|
||||||
|
"phrity/websocket": "^3.6",
|
||||||
"spatie/laravel-html": "^3.11",
|
"spatie/laravel-html": "^3.11",
|
||||||
"spatie/laravel-ignition": "^2.4",
|
"spatie/laravel-ignition": "^2.4",
|
||||||
"symfony/yaml": "^7.0"
|
"symfony/yaml": "^7.0"
|
||||||
|
|||||||
Generated
+599
-1
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "24b785f73e421451022a5e23d68cc6f0",
|
"content-hash": "90b6ed264d7efa958636840d86196b99",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
@@ -3536,6 +3536,84 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-02-16T23:10:27+00:00"
|
"time": "2026-02-16T23:10:27+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "nyholm/psr7",
|
||||||
|
"version": "1.8.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Nyholm/psr7.git",
|
||||||
|
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
|
||||||
|
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"php-http/message-factory-implementation": "1.0",
|
||||||
|
"psr/http-factory-implementation": "1.0",
|
||||||
|
"psr/http-message-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"http-interop/http-factory-tests": "^0.9",
|
||||||
|
"php-http/message-factory": "^1.0",
|
||||||
|
"php-http/psr7-integration-tests": "^1.0",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
|
||||||
|
"symfony/error-handler": "^4.4"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.8-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nyholm\\Psr7\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Martijn van der Ven",
|
||||||
|
"email": "martijn@vanderven.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A fast PHP7 implementation of PSR-7",
|
||||||
|
"homepage": "https://tnyholm.se",
|
||||||
|
"keywords": [
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Nyholm/psr7/issues",
|
||||||
|
"source": "https://github.com/Nyholm/psr7/tree/1.8.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Zegnat",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nyholm",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-09T07:06:30+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "php-http/cache-plugin",
|
"name": "php-http/cache-plugin",
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@@ -4046,6 +4124,526 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-12-27T19:41:33+00:00"
|
"time": "2025-12-27T19:41:33+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/comparison",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-comparison.git",
|
||||||
|
"reference": "cf80abb822537eeaaeb4142157cd667ca6372a13"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-comparison/zipball/cf80abb822537eeaaeb4142157cd667ca6372a13",
|
||||||
|
"reference": "cf80abb822537eeaaeb4142157cd667ca6372a13",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Comparison\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Interfaces and helper trait for comparing objects. Comparator for sort and filter applications.",
|
||||||
|
"homepage": "https://phrity.sirn.se/comparison",
|
||||||
|
"keywords": [
|
||||||
|
"comparable",
|
||||||
|
"comparator",
|
||||||
|
"comparison",
|
||||||
|
"equalable",
|
||||||
|
"filter",
|
||||||
|
"sort"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-comparison/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-comparison/tree/1.4.1"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T07:38:30+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/http",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-http.git",
|
||||||
|
"reference": "1e7eee67359287b94aae2b7d40b730d5f5394943"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-http/zipball/1e7eee67359287b94aae2b7d40b730d5f5394943",
|
||||||
|
"reference": "1e7eee67359287b94aae2b7d40b730d5f5394943",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/psr7": "^2.0",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Http\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Utilities and interfaces for handling HTTP.",
|
||||||
|
"homepage": "https://phrity.sirn.se/http",
|
||||||
|
"keywords": [
|
||||||
|
"HTTP Factories",
|
||||||
|
"HTTP Serializer",
|
||||||
|
"http",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-http/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-http/tree/1.1.0"
|
||||||
|
},
|
||||||
|
"time": "2025-12-22T20:22:29+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/net-stream",
|
||||||
|
"version": "2.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-net-stream.git",
|
||||||
|
"reference": "a0726a8dddf11a4cd3a9e45d17e145d78e948f43"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-net-stream/zipball/a0726a8dddf11a4cd3a9e45d17e145d78e948f43",
|
||||||
|
"reference": "a0726a8dddf11a4cd3a9e45d17e145d78e948f43",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"phrity/util-interpolator": "^1.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phrity/net-uri": "^2.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Net\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Socket stream classes implementing PSR-7 Stream and PSR-17 StreamFactory",
|
||||||
|
"homepage": "https://phrity.sirn.se/net-stream",
|
||||||
|
"keywords": [
|
||||||
|
"Socket",
|
||||||
|
"client",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7",
|
||||||
|
"server",
|
||||||
|
"stream",
|
||||||
|
"stream factory"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-net-stream/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-net-stream/tree/2.4.0"
|
||||||
|
},
|
||||||
|
"time": "2026-03-25T18:16:40+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/net-uri",
|
||||||
|
"version": "2.2.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-net-uri.git",
|
||||||
|
"reference": "0737de026b75177ae302ac9fdbbd0ffc2610f3b8"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-net-uri/zipball/0737de026b75177ae302ac9fdbbd0ffc2610f3b8",
|
||||||
|
"reference": "0737de026b75177ae302ac9fdbbd0ffc2610f3b8",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/comparison": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 | ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "Enables IDN conversion for non-ASCII domains"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Net\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PSR-7 Uri and PSR-17 UriFactory implementation",
|
||||||
|
"homepage": "https://phrity.sirn.se/net-uri",
|
||||||
|
"keywords": [
|
||||||
|
"psr-17",
|
||||||
|
"psr-7",
|
||||||
|
"uri",
|
||||||
|
"uri factory"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-net-uri/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-net-uri/tree/2.2.1"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T10:39:22+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-accessor",
|
||||||
|
"version": "1.3.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-accessor.git",
|
||||||
|
"reference": "e9741da6b532fa2da7f627fce60fa77f00f6e354"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-accessor/zipball/e9741da6b532fa2da7f627fce60fa77f00f6e354",
|
||||||
|
"reference": "e9741da6b532fa2da7f627fce60fa77f00f6e354",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-transformer": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Utility to handle access to a data set by using access paths. Similar to and partly compatible with xpath and json-pointer.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-accessor",
|
||||||
|
"keywords": [
|
||||||
|
"accessor"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-accessor/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-accessor/tree/1.3.1"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T21:18:15+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-errorhandler",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-errorhandler.git",
|
||||||
|
"reference": "70a669cc22db2eed6a109ec66fd95168a4332c9b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/70a669cc22db2eed6a109ec66fd95168a4332c9b",
|
||||||
|
"reference": "70a669cc22db2eed6a109ec66fd95168a4332c9b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Inline error handler; catch and resolve errors for code block.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-errorhandler",
|
||||||
|
"keywords": [
|
||||||
|
"error",
|
||||||
|
"warning"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-errorhandler/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.2.2"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T21:25:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-interpolator",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-interpolator.git",
|
||||||
|
"reference": "18b0362d2aff60984c530808333c5e64c80c3e50"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-interpolator/zipball/18b0362d2aff60984c530808333c5e64c80c3e50",
|
||||||
|
"reference": "18b0362d2aff60984c530808333c5e64c80c3e50",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-accessor": "^1.3",
|
||||||
|
"phrity/util-transformer": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Interpolator\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Interpolator class and trait.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-interpolator",
|
||||||
|
"keywords": [
|
||||||
|
"interpolate",
|
||||||
|
"interpolator"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-interpolator/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-interpolator/tree/1.1.1"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T21:35:25+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-transformer",
|
||||||
|
"version": "1.3.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-transformer.git",
|
||||||
|
"reference": "3fd4d7fa4077deafe5130c4b9ee8146b5488abe3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-transformer/zipball/3fd4d7fa4077deafe5130c4b9ee8146b5488abe3",
|
||||||
|
"reference": "3fd4d7fa4077deafe5130c4b9ee8146b5488abe3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0",
|
||||||
|
"symfony/property-access": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/serializer": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/translation-contracts": "^3.0",
|
||||||
|
"symfony/uid": "^6.0 || ^7.0 || ^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Transformer\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Type transformers, normalizers and resolvers.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-transformer",
|
||||||
|
"keywords": [
|
||||||
|
"normalizer",
|
||||||
|
"resolver",
|
||||||
|
"transformer",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-transformer/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-transformer/tree/1.3.1"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T22:07:04+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/websocket",
|
||||||
|
"version": "3.7.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/websocket-php.git",
|
||||||
|
"reference": "c92d613ec298ea108b8ebae306609cd40427f7d6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/websocket-php/zipball/c92d613ec298ea108b8ebae306609cd40427f7d6",
|
||||||
|
"reference": "c92d613ec298ea108b8ebae306609cd40427f7d6",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"nyholm/psr7": "^1.4",
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/http": "^1.1",
|
||||||
|
"phrity/net-stream": "^2.4",
|
||||||
|
"phrity/net-uri": "^2.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0",
|
||||||
|
"psr/log": "^1.0 || ^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phrity/logger-console": "^1.0",
|
||||||
|
"phrity/net-mock": "^2.4",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"WebSocket\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"ISC"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fredrik Liljegren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "WebSocket client and server",
|
||||||
|
"homepage": "https://phrity.sirn.se/websocket",
|
||||||
|
"keywords": [
|
||||||
|
"client",
|
||||||
|
"server",
|
||||||
|
"websocket"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/websocket-php/issues",
|
||||||
|
"source": "https://github.com/sirn-se/websocket-php/tree/3.7.3"
|
||||||
|
},
|
||||||
|
"time": "2026-06-22T14:53:45+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/cache",
|
"name": "psr/cache",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Facade;
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'version' => '2.8.0',
|
'version' => '2.8.1',
|
||||||
|
|
||||||
'appsource' => env('APP_SOURCE', 'https://appslist.heimdall.site/'),
|
'appsource' => env('APP_SOURCE', 'https://appslist.heimdall.site/'),
|
||||||
|
|
||||||
|
|||||||
@@ -364,5 +364,19 @@ class SettingsSeeder extends Seeder
|
|||||||
$setting->label = 'app.settings.default_tag';
|
$setting->label = 'app.settings.default_tag';
|
||||||
$setting->save();
|
$setting->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $setting = Setting::find(16)) {
|
||||||
|
$setting = new Setting;
|
||||||
|
$setting->id = 16;
|
||||||
|
$setting->group_id = 4;
|
||||||
|
$setting->key = 'skip_tls_verification';
|
||||||
|
$setting->type = 'boolean';
|
||||||
|
$setting->label = 'app.settings.skip_tls_verification';
|
||||||
|
$setting->value = '0';
|
||||||
|
$setting->save();
|
||||||
|
} else {
|
||||||
|
$setting->label = 'app.settings.skip_tls_verification';
|
||||||
|
$setting->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ return array (
|
|||||||
'settings.view' => 'Ansicht',
|
'settings.view' => 'Ansicht',
|
||||||
'settings.custom_css' => 'Angepasstes CSS',
|
'settings.custom_css' => 'Angepasstes CSS',
|
||||||
'settings.custom_js' => 'Angepasstes JavaScript',
|
'settings.custom_js' => 'Angepasstes JavaScript',
|
||||||
|
'settings.treat_tags_as' => 'Tags behandeln als:',
|
||||||
|
'settings.default_tag' => 'Standard Tag',
|
||||||
|
'settings.folders' => 'Ordner',
|
||||||
|
'settings.tags' => 'Tags',
|
||||||
|
'settings.categories' => 'Kategorien',
|
||||||
|
'settings.skip_tls_verification' => 'TLS-Überprüfung überspringen (selbstsignierte Zertifikate)',
|
||||||
'options.none' => '- nicht festgelegt -',
|
'options.none' => '- nicht festgelegt -',
|
||||||
'options.google' => 'Google',
|
'options.google' => 'Google',
|
||||||
'options.ddg' => 'DuckDuckGo',
|
'options.ddg' => 'DuckDuckGo',
|
||||||
@@ -74,6 +80,7 @@ return array (
|
|||||||
'apps.only_admin_account' => 'Nur mit Admin-Konto!',
|
'apps.only_admin_account' => 'Nur mit Admin-Konto!',
|
||||||
'apps.autologin_url' => 'Auto Login URL',
|
'apps.autologin_url' => 'Auto Login URL',
|
||||||
'apps.show_deleted' => 'Gelöschte Anwendung anzeigen',
|
'apps.show_deleted' => 'Gelöschte Anwendung anzeigen',
|
||||||
|
'app.import' => 'Importieren',
|
||||||
'dashboard' => 'Home Dashboard',
|
'dashboard' => 'Home Dashboard',
|
||||||
'user.user_list' => 'Nutzer',
|
'user.user_list' => 'Nutzer',
|
||||||
'user.add_user' => 'Nutzer hinzufügen',
|
'user.add_user' => 'Nutzer hinzufügen',
|
||||||
@@ -88,6 +95,8 @@ return array (
|
|||||||
'delete' => 'Löschen',
|
'delete' => 'Löschen',
|
||||||
'optional' => 'Optional',
|
'optional' => 'Optional',
|
||||||
'restore' => 'Wiederherstellen',
|
'restore' => 'Wiederherstellen',
|
||||||
|
'export' => 'Exportieren',
|
||||||
|
'import' => 'Importieren',
|
||||||
'alert.success.item_created' => 'Element erfolgreich erstellt',
|
'alert.success.item_created' => 'Element erfolgreich erstellt',
|
||||||
'alert.success.item_updated' => 'Element erfolgreich aktualisiert',
|
'alert.success.item_updated' => 'Element erfolgreich aktualisiert',
|
||||||
'alert.success.item_deleted' => 'Element erfolgreich gelöscht',
|
'alert.success.item_deleted' => 'Element erfolgreich gelöscht',
|
||||||
@@ -99,6 +108,8 @@ return array (
|
|||||||
'alert.success.tag_restored' => 'Tag erfolgreich wiederhergestellt',
|
'alert.success.tag_restored' => 'Tag erfolgreich wiederhergestellt',
|
||||||
'alert.success.setting_updated' => 'Die Einstellungen wurden übernommen',
|
'alert.success.setting_updated' => 'Die Einstellungen wurden übernommen',
|
||||||
'alert.error.not_exist' => 'Diese Einstellung existiert nicht.',
|
'alert.error.not_exist' => 'Diese Einstellung existiert nicht.',
|
||||||
|
'alert.error.file_too_big' => 'Datei zu groß',
|
||||||
|
'alert.error.file_not_stored' => 'Datei konnte nicht gespeichert werden',
|
||||||
'alert.success.user_created' => 'Nutzer erfolgreich erstellt',
|
'alert.success.user_created' => 'Nutzer erfolgreich erstellt',
|
||||||
'alert.success.user_updated' => 'Nutzer erfolgreich aktualisiert',
|
'alert.success.user_updated' => 'Nutzer erfolgreich aktualisiert',
|
||||||
'alert.success.user_deleted' => 'Nutzer erfolgreich gelöscht',
|
'alert.success.user_deleted' => 'Nutzer erfolgreich gelöscht',
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ return array (
|
|||||||
'settings.folders' => 'Folders',
|
'settings.folders' => 'Folders',
|
||||||
'settings.tags' => 'Tags',
|
'settings.tags' => 'Tags',
|
||||||
'settings.categories' => 'Categories',
|
'settings.categories' => 'Categories',
|
||||||
|
'settings.skip_tls_verification' => 'Skip TLS Verification (for self-signed certificates)',
|
||||||
'options.none' => '- not set -',
|
'options.none' => '- not set -',
|
||||||
'options.google' => 'Google',
|
'options.google' => 'Google',
|
||||||
'options.ddg' => 'DuckDuckGo',
|
'options.ddg' => 'DuckDuckGo',
|
||||||
|
|||||||
Vendored
+3
-2
@@ -927,9 +927,10 @@ div.create .input input, div.create .input select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-icon {
|
.app-icon {
|
||||||
max-width: 60px;
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
object-fit: contain;
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidenav {
|
.sidenav {
|
||||||
|
|||||||
Vendored
+6
-1
@@ -4271,7 +4271,12 @@ $.when($.ready).then(function () {
|
|||||||
data.url = apiurl;
|
data.url = apiurl;
|
||||||
$(".config-item").each(function () {
|
$(".config-item").each(function () {
|
||||||
var config = $(this).data("config");
|
var config = $(this).data("config");
|
||||||
data[config] = $(this).val();
|
// For checkboxes, use checked state instead of value attribute
|
||||||
|
if ($(this).is(":checkbox")) {
|
||||||
|
data[config] = $(this).is(":checked") ? "1" : "0";
|
||||||
|
} else {
|
||||||
|
data[config] = $(this).val();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
data.id = $("form[data-item-id]").data("item-id");
|
data.id = $("form[data-item-id]").data("item-id");
|
||||||
if (data.password && data.password === fakePassword) {
|
if (data.password && data.password === fakePassword) {
|
||||||
|
|||||||
@@ -310,7 +310,12 @@ $.when($.ready).then(() => {
|
|||||||
data.url = apiurl;
|
data.url = apiurl;
|
||||||
$(".config-item").each(function () {
|
$(".config-item").each(function () {
|
||||||
const config = $(this).data("config");
|
const config = $(this).data("config");
|
||||||
data[config] = $(this).val();
|
// For checkboxes, use checked state instead of value attribute
|
||||||
|
if ($(this).is(":checkbox")) {
|
||||||
|
data[config] = $(this).is(":checked") ? "1" : "0";
|
||||||
|
} else {
|
||||||
|
data[config] = $(this).val();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
data.id = $("form[data-item-id]").data("item-id");
|
data.id = $("form[data-item-id]").data("item-id");
|
||||||
|
|||||||
@@ -722,9 +722,10 @@ div.create {
|
|||||||
flex: 0 0 60px;
|
flex: 0 0 60px;
|
||||||
}
|
}
|
||||||
.app-icon {
|
.app-icon {
|
||||||
max-width: 60px;
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
object-fit: contain;
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidenav {
|
.sidenav {
|
||||||
|
|||||||
+127
@@ -33,6 +33,7 @@ return array(
|
|||||||
'App\\Console\\Commands\\RegisterApp' => $baseDir . '/app/Console/Commands/RegisterApp.php',
|
'App\\Console\\Commands\\RegisterApp' => $baseDir . '/app/Console/Commands/RegisterApp.php',
|
||||||
'App\\EnhancedApps' => $baseDir . '/app/EnhancedApps.php',
|
'App\\EnhancedApps' => $baseDir . '/app/EnhancedApps.php',
|
||||||
'App\\Facades\\Form' => $baseDir . '/app/Facades/Form.php',
|
'App\\Facades\\Form' => $baseDir . '/app/Facades/Form.php',
|
||||||
|
'App\\Helpers\\TrueNASWebSocketClient' => $baseDir . '/app/Helpers/TrueNASWebSocketClient.php',
|
||||||
'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => $baseDir . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
|
'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => $baseDir . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
|
||||||
'App\\Http\\Controllers\\Auth\\LoginController' => $baseDir . '/app/Http/Controllers/Auth/LoginController.php',
|
'App\\Http\\Controllers\\Auth\\LoginController' => $baseDir . '/app/Http/Controllers/Auth/LoginController.php',
|
||||||
'App\\Http\\Controllers\\Auth\\RegisterController' => $baseDir . '/app/Http/Controllers/Auth/RegisterController.php',
|
'App\\Http\\Controllers\\Auth\\RegisterController' => $baseDir . '/app/Http/Controllers/Auth/RegisterController.php',
|
||||||
@@ -5199,6 +5200,17 @@ return array(
|
|||||||
'NunoMaduro\\Collision\\Provider' => $vendorDir . '/nunomaduro/collision/src/Provider.php',
|
'NunoMaduro\\Collision\\Provider' => $vendorDir . '/nunomaduro/collision/src/Provider.php',
|
||||||
'NunoMaduro\\Collision\\SolutionsRepositories\\NullSolutionsRepository' => $vendorDir . '/nunomaduro/collision/src/SolutionsRepositories/NullSolutionsRepository.php',
|
'NunoMaduro\\Collision\\SolutionsRepositories\\NullSolutionsRepository' => $vendorDir . '/nunomaduro/collision/src/SolutionsRepositories/NullSolutionsRepository.php',
|
||||||
'NunoMaduro\\Collision\\Writer' => $vendorDir . '/nunomaduro/collision/src/Writer.php',
|
'NunoMaduro\\Collision\\Writer' => $vendorDir . '/nunomaduro/collision/src/Writer.php',
|
||||||
|
'Nyholm\\Psr7\\Factory\\HttplugFactory' => $vendorDir . '/nyholm/psr7/src/Factory/HttplugFactory.php',
|
||||||
|
'Nyholm\\Psr7\\Factory\\Psr17Factory' => $vendorDir . '/nyholm/psr7/src/Factory/Psr17Factory.php',
|
||||||
|
'Nyholm\\Psr7\\MessageTrait' => $vendorDir . '/nyholm/psr7/src/MessageTrait.php',
|
||||||
|
'Nyholm\\Psr7\\Request' => $vendorDir . '/nyholm/psr7/src/Request.php',
|
||||||
|
'Nyholm\\Psr7\\RequestTrait' => $vendorDir . '/nyholm/psr7/src/RequestTrait.php',
|
||||||
|
'Nyholm\\Psr7\\Response' => $vendorDir . '/nyholm/psr7/src/Response.php',
|
||||||
|
'Nyholm\\Psr7\\ServerRequest' => $vendorDir . '/nyholm/psr7/src/ServerRequest.php',
|
||||||
|
'Nyholm\\Psr7\\Stream' => $vendorDir . '/nyholm/psr7/src/Stream.php',
|
||||||
|
'Nyholm\\Psr7\\StreamTrait' => $vendorDir . '/nyholm/psr7/src/StreamTrait.php',
|
||||||
|
'Nyholm\\Psr7\\UploadedFile' => $vendorDir . '/nyholm/psr7/src/UploadedFile.php',
|
||||||
|
'Nyholm\\Psr7\\Uri' => $vendorDir . '/nyholm/psr7/src/Uri.php',
|
||||||
'PHPUnit\\Event\\Application\\Finished' => $vendorDir . '/phpunit/phpunit/src/Event/Events/Application/Finished.php',
|
'PHPUnit\\Event\\Application\\Finished' => $vendorDir . '/phpunit/phpunit/src/Event/Events/Application/Finished.php',
|
||||||
'PHPUnit\\Event\\Application\\FinishedSubscriber' => $vendorDir . '/phpunit/phpunit/src/Event/Events/Application/FinishedSubscriber.php',
|
'PHPUnit\\Event\\Application\\FinishedSubscriber' => $vendorDir . '/phpunit/phpunit/src/Event/Events/Application/FinishedSubscriber.php',
|
||||||
'PHPUnit\\Event\\Application\\Started' => $vendorDir . '/phpunit/phpunit/src/Event/Events/Application/Started.php',
|
'PHPUnit\\Event\\Application\\Started' => $vendorDir . '/phpunit/phpunit/src/Event/Events/Application/Started.php',
|
||||||
@@ -6553,6 +6565,51 @@ return array(
|
|||||||
'PhpParser\\PrettyPrinter\\Standard' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php',
|
'PhpParser\\PrettyPrinter\\Standard' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php',
|
||||||
'PhpParser\\Token' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Token.php',
|
'PhpParser\\Token' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Token.php',
|
||||||
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||||
|
'Phrity\\Comparison\\Comparable' => $vendorDir . '/phrity/comparison/src/Comparable.php',
|
||||||
|
'Phrity\\Comparison\\Comparator' => $vendorDir . '/phrity/comparison/src/Comparator.php',
|
||||||
|
'Phrity\\Comparison\\ComparisonTrait' => $vendorDir . '/phrity/comparison/src/ComparisonTrait.php',
|
||||||
|
'Phrity\\Comparison\\Equalable' => $vendorDir . '/phrity/comparison/src/Equalable.php',
|
||||||
|
'Phrity\\Comparison\\IncomparableException' => $vendorDir . '/phrity/comparison/src/IncomparableException.php',
|
||||||
|
'Phrity\\Http\\HttpFactory' => $vendorDir . '/phrity/http/src/HttpFactory.php',
|
||||||
|
'Phrity\\Http\\Serializer' => $vendorDir . '/phrity/http/src/Serializer.php',
|
||||||
|
'Phrity\\Net\\Context' => $vendorDir . '/phrity/net-stream/src/Context.php',
|
||||||
|
'Phrity\\Net\\SocketClient' => $vendorDir . '/phrity/net-stream/src/SocketClient.php',
|
||||||
|
'Phrity\\Net\\SocketServer' => $vendorDir . '/phrity/net-stream/src/SocketServer.php',
|
||||||
|
'Phrity\\Net\\SocketStream' => $vendorDir . '/phrity/net-stream/src/SocketStream.php',
|
||||||
|
'Phrity\\Net\\Stream' => $vendorDir . '/phrity/net-stream/src/Stream.php',
|
||||||
|
'Phrity\\Net\\StreamCollection' => $vendorDir . '/phrity/net-stream/src/StreamCollection.php',
|
||||||
|
'Phrity\\Net\\StreamContainerInterface' => $vendorDir . '/phrity/net-stream/src/StreamContainerInterface.php',
|
||||||
|
'Phrity\\Net\\StreamException' => $vendorDir . '/phrity/net-stream/src/StreamException.php',
|
||||||
|
'Phrity\\Net\\StreamFactory' => $vendorDir . '/phrity/net-stream/src/StreamFactory.php',
|
||||||
|
'Phrity\\Net\\StreamInterface' => $vendorDir . '/phrity/net-stream/src/StreamInterface.php',
|
||||||
|
'Phrity\\Net\\Uri' => $vendorDir . '/phrity/net-uri/src/Uri.php',
|
||||||
|
'Phrity\\Net\\UriFactory' => $vendorDir . '/phrity/net-uri/src/UriFactory.php',
|
||||||
|
'Phrity\\Util\\Accessor' => $vendorDir . '/phrity/util-accessor/src/Accessor.php',
|
||||||
|
'Phrity\\Util\\AccessorException' => $vendorDir . '/phrity/util-accessor/src/AccessorException.php',
|
||||||
|
'Phrity\\Util\\AccessorTrait' => $vendorDir . '/phrity/util-accessor/src/AccessorTrait.php',
|
||||||
|
'Phrity\\Util\\DataAccessor' => $vendorDir . '/phrity/util-accessor/src/DataAccessor.php',
|
||||||
|
'Phrity\\Util\\ErrorHandler' => $vendorDir . '/phrity/util-errorhandler/src/ErrorHandler.php',
|
||||||
|
'Phrity\\Util\\Interpolator\\Interpolator' => $vendorDir . '/phrity/util-interpolator/src/Interpolator.php',
|
||||||
|
'Phrity\\Util\\Interpolator\\InterpolatorTrait' => $vendorDir . '/phrity/util-interpolator/src/InterpolatorTrait.php',
|
||||||
|
'Phrity\\Util\\PathAccessor' => $vendorDir . '/phrity/util-accessor/src/PathAccessor.php',
|
||||||
|
'Phrity\\Util\\Transformer\\BasicTypeConverter' => $vendorDir . '/phrity/util-transformer/src/BasicTypeConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ChainedResolver' => $vendorDir . '/phrity/util-transformer/src/ChainedResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\DateTimeConverter' => $vendorDir . '/phrity/util-transformer/src/DateTimeConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\EnumConverter' => $vendorDir . '/phrity/util-transformer/src/EnumConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\FirstMatchResolver' => $vendorDir . '/phrity/util-transformer/src/FirstMatchResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\FlattenDecoder' => $vendorDir . '/phrity/util-transformer/src/FlattenDecoder.php',
|
||||||
|
'Phrity\\Util\\Transformer\\JsonDecoder' => $vendorDir . '/phrity/util-transformer/src/JsonDecoder.php',
|
||||||
|
'Phrity\\Util\\Transformer\\JsonSerializableConverter' => $vendorDir . '/phrity/util-transformer/src/JsonSerializableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ReadableConverter' => $vendorDir . '/phrity/util-transformer/src/ReadableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\RecursionResolver' => $vendorDir . '/phrity/util-transformer/src/RecursionResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ReversedReadableConverter' => $vendorDir . '/phrity/util-transformer/src/ReversedReadableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\StringResolver' => $vendorDir . '/phrity/util-transformer/src/StringResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\StringableConverter' => $vendorDir . '/phrity/util-transformer/src/StringableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\Symfony\\NormalizerWrapper' => $vendorDir . '/phrity/util-transformer/src/Symfony/NormalizerWrapper.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ThrowableConverter' => $vendorDir . '/phrity/util-transformer/src/ThrowableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\TransformerException' => $vendorDir . '/phrity/util-transformer/src/TransformerException.php',
|
||||||
|
'Phrity\\Util\\Transformer\\TransformerInterface' => $vendorDir . '/phrity/util-transformer/src/TransformerInterface.php',
|
||||||
|
'Phrity\\Util\\Transformer\\Type' => $vendorDir . '/phrity/util-transformer/src/Type.php',
|
||||||
'Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php',
|
'Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php',
|
||||||
'Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php',
|
'Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php',
|
||||||
'Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php',
|
'Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php',
|
||||||
@@ -8851,6 +8908,8 @@ return array(
|
|||||||
'Termwind\\ValueObjects\\Node' => $vendorDir . '/nunomaduro/termwind/src/ValueObjects/Node.php',
|
'Termwind\\ValueObjects\\Node' => $vendorDir . '/nunomaduro/termwind/src/ValueObjects/Node.php',
|
||||||
'Termwind\\ValueObjects\\Style' => $vendorDir . '/nunomaduro/termwind/src/ValueObjects/Style.php',
|
'Termwind\\ValueObjects\\Style' => $vendorDir . '/nunomaduro/termwind/src/ValueObjects/Style.php',
|
||||||
'Termwind\\ValueObjects\\Styles' => $vendorDir . '/nunomaduro/termwind/src/ValueObjects/Styles.php',
|
'Termwind\\ValueObjects\\Styles' => $vendorDir . '/nunomaduro/termwind/src/ValueObjects/Styles.php',
|
||||||
|
'Tests\\Feature\\AjaxPostEndpointsTest' => $baseDir . '/tests/Feature/AjaxPostEndpointsTest.php',
|
||||||
|
'Tests\\Feature\\CsrfExceptionsTest' => $baseDir . '/tests/Feature/CsrfExceptionsTest.php',
|
||||||
'Tests\\Feature\\DashTest' => $baseDir . '/tests/Feature/DashTest.php',
|
'Tests\\Feature\\DashTest' => $baseDir . '/tests/Feature/DashTest.php',
|
||||||
'Tests\\Feature\\GetStatsTest' => $baseDir . '/tests/Feature/GetStatsTest.php',
|
'Tests\\Feature\\GetStatsTest' => $baseDir . '/tests/Feature/GetStatsTest.php',
|
||||||
'Tests\\Feature\\ImportTest' => $baseDir . '/tests/Feature/ImportTest.php',
|
'Tests\\Feature\\ImportTest' => $baseDir . '/tests/Feature/ImportTest.php',
|
||||||
@@ -8859,16 +8918,24 @@ return array(
|
|||||||
'Tests\\Feature\\ItemExportTest' => $baseDir . '/tests/Feature/ItemExportTest.php',
|
'Tests\\Feature\\ItemExportTest' => $baseDir . '/tests/Feature/ItemExportTest.php',
|
||||||
'Tests\\Feature\\ItemImportTest' => $baseDir . '/tests/Feature/ItemImportTest.php',
|
'Tests\\Feature\\ItemImportTest' => $baseDir . '/tests/Feature/ItemImportTest.php',
|
||||||
'Tests\\Feature\\ItemListTest' => $baseDir . '/tests/Feature/ItemListTest.php',
|
'Tests\\Feature\\ItemListTest' => $baseDir . '/tests/Feature/ItemListTest.php',
|
||||||
|
'Tests\\Feature\\ItemOwnershipTest' => $baseDir . '/tests/Feature/ItemOwnershipTest.php',
|
||||||
|
'Tests\\Feature\\OrphanItemRecoveryTest' => $baseDir . '/tests/Feature/OrphanItemRecoveryTest.php',
|
||||||
|
'Tests\\Feature\\RoutesRenderTest' => $baseDir . '/tests/Feature/RoutesRenderTest.php',
|
||||||
'Tests\\Feature\\SearchTest' => $baseDir . '/tests/Feature/SearchTest.php',
|
'Tests\\Feature\\SearchTest' => $baseDir . '/tests/Feature/SearchTest.php',
|
||||||
'Tests\\Feature\\SettingsTest' => $baseDir . '/tests/Feature/SettingsTest.php',
|
'Tests\\Feature\\SettingsTest' => $baseDir . '/tests/Feature/SettingsTest.php',
|
||||||
|
'Tests\\Feature\\StorageDiskTest' => $baseDir . '/tests/Feature/StorageDiskTest.php',
|
||||||
'Tests\\Feature\\TagListTest' => $baseDir . '/tests/Feature/TagListTest.php',
|
'Tests\\Feature\\TagListTest' => $baseDir . '/tests/Feature/TagListTest.php',
|
||||||
'Tests\\Feature\\TrustHostsTest' => $baseDir . '/tests/Feature/TrustHostsTest.php',
|
'Tests\\Feature\\TrustHostsTest' => $baseDir . '/tests/Feature/TrustHostsTest.php',
|
||||||
'Tests\\Feature\\TrustProxiesTest' => $baseDir . '/tests/Feature/TrustProxiesTest.php',
|
'Tests\\Feature\\TrustProxiesTest' => $baseDir . '/tests/Feature/TrustProxiesTest.php',
|
||||||
|
'Tests\\Feature\\UserDeleteItemsTest' => $baseDir . '/tests/Feature/UserDeleteItemsTest.php',
|
||||||
'Tests\\Feature\\UserEditTest' => $baseDir . '/tests/Feature/UserEditTest.php',
|
'Tests\\Feature\\UserEditTest' => $baseDir . '/tests/Feature/UserEditTest.php',
|
||||||
'Tests\\Feature\\UserListTest' => $baseDir . '/tests/Feature/UserListTest.php',
|
'Tests\\Feature\\UserListTest' => $baseDir . '/tests/Feature/UserListTest.php',
|
||||||
'Tests\\TestCase' => $baseDir . '/tests/TestCase.php',
|
'Tests\\TestCase' => $baseDir . '/tests/TestCase.php',
|
||||||
'Tests\\Unit\\database\\seeders\\SettingsSeederTest' => $baseDir . '/tests/Unit/database/seeders/SettingsSeederTest.php',
|
'Tests\\Unit\\database\\seeders\\SettingsSeederTest' => $baseDir . '/tests/Unit/database/seeders/SettingsSeederTest.php',
|
||||||
|
'Tests\\Unit\\helpers\\ClassNameTest' => $baseDir . '/tests/Unit/helpers/ClassNameTest.php',
|
||||||
|
'Tests\\Unit\\helpers\\ColorHelpersTest' => $baseDir . '/tests/Unit/helpers/ColorHelpersTest.php',
|
||||||
'Tests\\Unit\\helpers\\IsImageTest' => $baseDir . '/tests/Unit/helpers/IsImageTest.php',
|
'Tests\\Unit\\helpers\\IsImageTest' => $baseDir . '/tests/Unit/helpers/IsImageTest.php',
|
||||||
|
'Tests\\Unit\\helpers\\SizeHelpersTest' => $baseDir . '/tests/Unit/helpers/SizeHelpersTest.php',
|
||||||
'Tests\\Unit\\helpers\\SlugTest' => $baseDir . '/tests/Unit/helpers/SlugTest.php',
|
'Tests\\Unit\\helpers\\SlugTest' => $baseDir . '/tests/Unit/helpers/SlugTest.php',
|
||||||
'Tests\\Unit\\lang\\LangTest' => $baseDir . '/tests/Unit/lang/LangTest.php',
|
'Tests\\Unit\\lang\\LangTest' => $baseDir . '/tests/Unit/lang/LangTest.php',
|
||||||
'TheSeer\\Tokenizer\\Exception' => $vendorDir . '/theseer/tokenizer/src/Exception.php',
|
'TheSeer\\Tokenizer\\Exception' => $vendorDir . '/theseer/tokenizer/src/Exception.php',
|
||||||
@@ -8887,6 +8954,66 @@ return array(
|
|||||||
'TijsVerkoyen\\CssToInlineStyles\\Css\\Rule\\Rule' => $vendorDir . '/tijsverkoyen/css-to-inline-styles/src/Css/Rule/Rule.php',
|
'TijsVerkoyen\\CssToInlineStyles\\Css\\Rule\\Rule' => $vendorDir . '/tijsverkoyen/css-to-inline-styles/src/Css/Rule/Rule.php',
|
||||||
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||||
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
||||||
|
'WebSocket\\Client' => $vendorDir . '/phrity/websocket/src/Client.php',
|
||||||
|
'WebSocket\\Configuration' => $vendorDir . '/phrity/websocket/src/Configuration.php',
|
||||||
|
'WebSocket\\Connection' => $vendorDir . '/phrity/websocket/src/Connection.php',
|
||||||
|
'WebSocket\\Constant' => $vendorDir . '/phrity/websocket/src/Constant.php',
|
||||||
|
'WebSocket\\Exception\\BadOpcodeException' => $vendorDir . '/phrity/websocket/src/Exception/BadOpcodeException.php',
|
||||||
|
'WebSocket\\Exception\\BadUriException' => $vendorDir . '/phrity/websocket/src/Exception/BadUriException.php',
|
||||||
|
'WebSocket\\Exception\\ClientException' => $vendorDir . '/phrity/websocket/src/Exception/ClientException.php',
|
||||||
|
'WebSocket\\Exception\\CloseException' => $vendorDir . '/phrity/websocket/src/Exception/CloseException.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionClosedException' => $vendorDir . '/phrity/websocket/src/Exception/ConnectionClosedException.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionFailureException' => $vendorDir . '/phrity/websocket/src/Exception/ConnectionFailureException.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionLevelInterface' => $vendorDir . '/phrity/websocket/src/Exception/ConnectionLevelInterface.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionTimeoutException' => $vendorDir . '/phrity/websocket/src/Exception/ConnectionTimeoutException.php',
|
||||||
|
'WebSocket\\Exception\\Exception' => $vendorDir . '/phrity/websocket/src/Exception/Exception.php',
|
||||||
|
'WebSocket\\Exception\\ExceptionInterface' => $vendorDir . '/phrity/websocket/src/Exception/ExceptionInterface.php',
|
||||||
|
'WebSocket\\Exception\\HandshakeException' => $vendorDir . '/phrity/websocket/src/Exception/HandshakeException.php',
|
||||||
|
'WebSocket\\Exception\\MessageLevelInterface' => $vendorDir . '/phrity/websocket/src/Exception/MessageLevelInterface.php',
|
||||||
|
'WebSocket\\Exception\\ReconnectException' => $vendorDir . '/phrity/websocket/src/Exception/ReconnectException.php',
|
||||||
|
'WebSocket\\Exception\\RunnerException' => $vendorDir . '/phrity/websocket/src/Exception/RunnerException.php',
|
||||||
|
'WebSocket\\Exception\\ServerException' => $vendorDir . '/phrity/websocket/src/Exception/ServerException.php',
|
||||||
|
'WebSocket\\Frame\\Frame' => $vendorDir . '/phrity/websocket/src/Frame/Frame.php',
|
||||||
|
'WebSocket\\Frame\\FrameHandler' => $vendorDir . '/phrity/websocket/src/Frame/FrameHandler.php',
|
||||||
|
'WebSocket\\Http\\DefaultHttpFactory' => $vendorDir . '/phrity/websocket/src/Http/DefaultHttpFactory.php',
|
||||||
|
'WebSocket\\Http\\HttpHandler' => $vendorDir . '/phrity/websocket/src/Http/HttpHandler.php',
|
||||||
|
'WebSocket\\Http\\Request' => $vendorDir . '/phrity/websocket/src/Http/Request.php',
|
||||||
|
'WebSocket\\Http\\Response' => $vendorDir . '/phrity/websocket/src/Http/Response.php',
|
||||||
|
'WebSocket\\Http\\ServerRequest' => $vendorDir . '/phrity/websocket/src/Http/ServerRequest.php',
|
||||||
|
'WebSocket\\Message\\Binary' => $vendorDir . '/phrity/websocket/src/Message/Binary.php',
|
||||||
|
'WebSocket\\Message\\Close' => $vendorDir . '/phrity/websocket/src/Message/Close.php',
|
||||||
|
'WebSocket\\Message\\Message' => $vendorDir . '/phrity/websocket/src/Message/Message.php',
|
||||||
|
'WebSocket\\Message\\MessageHandler' => $vendorDir . '/phrity/websocket/src/Message/MessageHandler.php',
|
||||||
|
'WebSocket\\Message\\Ping' => $vendorDir . '/phrity/websocket/src/Message/Ping.php',
|
||||||
|
'WebSocket\\Message\\Pong' => $vendorDir . '/phrity/websocket/src/Message/Pong.php',
|
||||||
|
'WebSocket\\Message\\Text' => $vendorDir . '/phrity/websocket/src/Message/Text.php',
|
||||||
|
'WebSocket\\Middleware\\Callback' => $vendorDir . '/phrity/websocket/src/Middleware/Callback.php',
|
||||||
|
'WebSocket\\Middleware\\CloseHandler' => $vendorDir . '/phrity/websocket/src/Middleware/CloseHandler.php',
|
||||||
|
'WebSocket\\Middleware\\CompressionExtension' => $vendorDir . '/phrity/websocket/src/Middleware/CompressionExtension.php',
|
||||||
|
'WebSocket\\Middleware\\CompressionExtension\\CompressorInterface' => $vendorDir . '/phrity/websocket/src/Middleware/CompressionExtension/CompressorInterface.php',
|
||||||
|
'WebSocket\\Middleware\\CompressionExtension\\DeflateCompressor' => $vendorDir . '/phrity/websocket/src/Middleware/CompressionExtension/DeflateCompressor.php',
|
||||||
|
'WebSocket\\Middleware\\FollowRedirect' => $vendorDir . '/phrity/websocket/src/Middleware/FollowRedirect.php',
|
||||||
|
'WebSocket\\Middleware\\MiddlewareHandler' => $vendorDir . '/phrity/websocket/src/Middleware/MiddlewareHandler.php',
|
||||||
|
'WebSocket\\Middleware\\MiddlewareInterface' => $vendorDir . '/phrity/websocket/src/Middleware/MiddlewareInterface.php',
|
||||||
|
'WebSocket\\Middleware\\PingInterval' => $vendorDir . '/phrity/websocket/src/Middleware/PingInterval.php',
|
||||||
|
'WebSocket\\Middleware\\PingResponder' => $vendorDir . '/phrity/websocket/src/Middleware/PingResponder.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessHttpIncomingInterface' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessHttpIncomingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessHttpOutgoingInterface' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessHttpOutgoingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessHttpStack' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessHttpStack.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessIncomingInterface' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessIncomingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessOutgoingInterface' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessOutgoingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessStack' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessStack.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessTickInterface' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessTickInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessTickStack' => $vendorDir . '/phrity/websocket/src/Middleware/ProcessTickStack.php',
|
||||||
|
'WebSocket\\Middleware\\SubprotocolNegotiation' => $vendorDir . '/phrity/websocket/src/Middleware/SubprotocolNegotiation.php',
|
||||||
|
'WebSocket\\Runtime\\IdentityInterface' => $vendorDir . '/phrity/websocket/src/Runtime/IdentityInterface.php',
|
||||||
|
'WebSocket\\Runtime\\Runner' => $vendorDir . '/phrity/websocket/src/Runtime/Runner.php',
|
||||||
|
'WebSocket\\Server' => $vendorDir . '/phrity/websocket/src/Server.php',
|
||||||
|
'WebSocket\\Trait\\ConfigurationTrait' => $vendorDir . '/phrity/websocket/src/Trait/ConfigurationTrait.php',
|
||||||
|
'WebSocket\\Trait\\ListenerTrait' => $vendorDir . '/phrity/websocket/src/Trait/ListenerTrait.php',
|
||||||
|
'WebSocket\\Trait\\OpcodeTrait' => $vendorDir . '/phrity/websocket/src/Trait/OpcodeTrait.php',
|
||||||
|
'WebSocket\\Trait\\SendMethodsTrait' => $vendorDir . '/phrity/websocket/src/Trait/SendMethodsTrait.php',
|
||||||
|
'WebSocket\\Trait\\StringableTrait' => $vendorDir . '/phrity/websocket/src/Trait/StringableTrait.php',
|
||||||
'Whoops\\Exception\\ErrorException' => $vendorDir . '/filp/whoops/src/Whoops/Exception/ErrorException.php',
|
'Whoops\\Exception\\ErrorException' => $vendorDir . '/filp/whoops/src/Whoops/Exception/ErrorException.php',
|
||||||
'Whoops\\Exception\\Formatter' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Formatter.php',
|
'Whoops\\Exception\\Formatter' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Formatter.php',
|
||||||
'Whoops\\Exception\\Frame' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Frame.php',
|
'Whoops\\Exception\\Frame' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Frame.php',
|
||||||
|
|||||||
Vendored
+8
@@ -9,6 +9,7 @@ return array(
|
|||||||
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
|
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
|
||||||
'enshrined\\svgSanitize\\' => array($vendorDir . '/enshrined/svg-sanitize/src'),
|
'enshrined\\svgSanitize\\' => array($vendorDir . '/enshrined/svg-sanitize/src'),
|
||||||
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
|
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
|
||||||
|
'WebSocket\\' => array($vendorDir . '/phrity/websocket/src'),
|
||||||
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
|
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
|
||||||
'Tests\\' => array($baseDir . '/tests'),
|
'Tests\\' => array($baseDir . '/tests'),
|
||||||
'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'),
|
'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'),
|
||||||
@@ -66,8 +67,15 @@ return array(
|
|||||||
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
|
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
|
||||||
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
|
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
|
||||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||||
|
'Phrity\\Util\\Transformer\\' => array($vendorDir . '/phrity/util-transformer/src'),
|
||||||
|
'Phrity\\Util\\Interpolator\\' => array($vendorDir . '/phrity/util-interpolator/src'),
|
||||||
|
'Phrity\\Util\\' => array($vendorDir . '/phrity/util-errorhandler/src', $vendorDir . '/phrity/util-accessor/src'),
|
||||||
|
'Phrity\\Net\\' => array($vendorDir . '/phrity/net-uri/src', $vendorDir . '/phrity/net-stream/src'),
|
||||||
|
'Phrity\\Http\\' => array($vendorDir . '/phrity/http/src'),
|
||||||
|
'Phrity\\Comparison\\' => array($vendorDir . '/phrity/comparison/src'),
|
||||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
||||||
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
|
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
|
||||||
|
'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'),
|
||||||
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'),
|
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'),
|
||||||
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
|
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
|
||||||
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
|
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
|
||||||
|
|||||||
Vendored
+169
@@ -65,6 +65,7 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'W' =>
|
'W' =>
|
||||||
array (
|
array (
|
||||||
'Whoops\\' => 7,
|
'Whoops\\' => 7,
|
||||||
|
'WebSocket\\' => 10,
|
||||||
),
|
),
|
||||||
'T' =>
|
'T' =>
|
||||||
array (
|
array (
|
||||||
@@ -134,11 +135,18 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'Psr\\Container\\' => 14,
|
'Psr\\Container\\' => 14,
|
||||||
'Psr\\Clock\\' => 10,
|
'Psr\\Clock\\' => 10,
|
||||||
'Psr\\Cache\\' => 10,
|
'Psr\\Cache\\' => 10,
|
||||||
|
'Phrity\\Util\\Transformer\\' => 24,
|
||||||
|
'Phrity\\Util\\Interpolator\\' => 25,
|
||||||
|
'Phrity\\Util\\' => 12,
|
||||||
|
'Phrity\\Net\\' => 11,
|
||||||
|
'Phrity\\Http\\' => 12,
|
||||||
|
'Phrity\\Comparison\\' => 18,
|
||||||
'PhpParser\\' => 10,
|
'PhpParser\\' => 10,
|
||||||
'PhpOption\\' => 10,
|
'PhpOption\\' => 10,
|
||||||
),
|
),
|
||||||
'N' =>
|
'N' =>
|
||||||
array (
|
array (
|
||||||
|
'Nyholm\\Psr7\\' => 12,
|
||||||
'NunoMaduro\\Collision\\' => 21,
|
'NunoMaduro\\Collision\\' => 21,
|
||||||
'Nette\\' => 6,
|
'Nette\\' => 6,
|
||||||
),
|
),
|
||||||
@@ -247,6 +255,10 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/filp/whoops/src/Whoops',
|
0 => __DIR__ . '/..' . '/filp/whoops/src/Whoops',
|
||||||
),
|
),
|
||||||
|
'WebSocket\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/phrity/websocket/src',
|
||||||
|
),
|
||||||
'TijsVerkoyen\\CssToInlineStyles\\' =>
|
'TijsVerkoyen\\CssToInlineStyles\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/tijsverkoyen/css-to-inline-styles/src',
|
0 => __DIR__ . '/..' . '/tijsverkoyen/css-to-inline-styles/src',
|
||||||
@@ -478,6 +490,32 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/psr/cache/src',
|
0 => __DIR__ . '/..' . '/psr/cache/src',
|
||||||
),
|
),
|
||||||
|
'Phrity\\Util\\Transformer\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/phrity/util-transformer/src',
|
||||||
|
),
|
||||||
|
'Phrity\\Util\\Interpolator\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/phrity/util-interpolator/src',
|
||||||
|
),
|
||||||
|
'Phrity\\Util\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/phrity/util-errorhandler/src',
|
||||||
|
1 => __DIR__ . '/..' . '/phrity/util-accessor/src',
|
||||||
|
),
|
||||||
|
'Phrity\\Net\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/phrity/net-uri/src',
|
||||||
|
1 => __DIR__ . '/..' . '/phrity/net-stream/src',
|
||||||
|
),
|
||||||
|
'Phrity\\Http\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/phrity/http/src',
|
||||||
|
),
|
||||||
|
'Phrity\\Comparison\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/phrity/comparison/src',
|
||||||
|
),
|
||||||
'PhpParser\\' =>
|
'PhpParser\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser',
|
0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser',
|
||||||
@@ -486,6 +524,10 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/phpoption/phpoption/src/PhpOption',
|
0 => __DIR__ . '/..' . '/phpoption/phpoption/src/PhpOption',
|
||||||
),
|
),
|
||||||
|
'Nyholm\\Psr7\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/nyholm/psr7/src',
|
||||||
|
),
|
||||||
'NunoMaduro\\Collision\\' =>
|
'NunoMaduro\\Collision\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/nunomaduro/collision/src',
|
0 => __DIR__ . '/..' . '/nunomaduro/collision/src',
|
||||||
@@ -755,6 +797,7 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'App\\Console\\Commands\\RegisterApp' => __DIR__ . '/../..' . '/app/Console/Commands/RegisterApp.php',
|
'App\\Console\\Commands\\RegisterApp' => __DIR__ . '/../..' . '/app/Console/Commands/RegisterApp.php',
|
||||||
'App\\EnhancedApps' => __DIR__ . '/../..' . '/app/EnhancedApps.php',
|
'App\\EnhancedApps' => __DIR__ . '/../..' . '/app/EnhancedApps.php',
|
||||||
'App\\Facades\\Form' => __DIR__ . '/../..' . '/app/Facades/Form.php',
|
'App\\Facades\\Form' => __DIR__ . '/../..' . '/app/Facades/Form.php',
|
||||||
|
'App\\Helpers\\TrueNASWebSocketClient' => __DIR__ . '/../..' . '/app/Helpers/TrueNASWebSocketClient.php',
|
||||||
'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
|
'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
|
||||||
'App\\Http\\Controllers\\Auth\\LoginController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',
|
'App\\Http\\Controllers\\Auth\\LoginController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',
|
||||||
'App\\Http\\Controllers\\Auth\\RegisterController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php',
|
'App\\Http\\Controllers\\Auth\\RegisterController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php',
|
||||||
@@ -5921,6 +5964,17 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'NunoMaduro\\Collision\\Provider' => __DIR__ . '/..' . '/nunomaduro/collision/src/Provider.php',
|
'NunoMaduro\\Collision\\Provider' => __DIR__ . '/..' . '/nunomaduro/collision/src/Provider.php',
|
||||||
'NunoMaduro\\Collision\\SolutionsRepositories\\NullSolutionsRepository' => __DIR__ . '/..' . '/nunomaduro/collision/src/SolutionsRepositories/NullSolutionsRepository.php',
|
'NunoMaduro\\Collision\\SolutionsRepositories\\NullSolutionsRepository' => __DIR__ . '/..' . '/nunomaduro/collision/src/SolutionsRepositories/NullSolutionsRepository.php',
|
||||||
'NunoMaduro\\Collision\\Writer' => __DIR__ . '/..' . '/nunomaduro/collision/src/Writer.php',
|
'NunoMaduro\\Collision\\Writer' => __DIR__ . '/..' . '/nunomaduro/collision/src/Writer.php',
|
||||||
|
'Nyholm\\Psr7\\Factory\\HttplugFactory' => __DIR__ . '/..' . '/nyholm/psr7/src/Factory/HttplugFactory.php',
|
||||||
|
'Nyholm\\Psr7\\Factory\\Psr17Factory' => __DIR__ . '/..' . '/nyholm/psr7/src/Factory/Psr17Factory.php',
|
||||||
|
'Nyholm\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/nyholm/psr7/src/MessageTrait.php',
|
||||||
|
'Nyholm\\Psr7\\Request' => __DIR__ . '/..' . '/nyholm/psr7/src/Request.php',
|
||||||
|
'Nyholm\\Psr7\\RequestTrait' => __DIR__ . '/..' . '/nyholm/psr7/src/RequestTrait.php',
|
||||||
|
'Nyholm\\Psr7\\Response' => __DIR__ . '/..' . '/nyholm/psr7/src/Response.php',
|
||||||
|
'Nyholm\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/nyholm/psr7/src/ServerRequest.php',
|
||||||
|
'Nyholm\\Psr7\\Stream' => __DIR__ . '/..' . '/nyholm/psr7/src/Stream.php',
|
||||||
|
'Nyholm\\Psr7\\StreamTrait' => __DIR__ . '/..' . '/nyholm/psr7/src/StreamTrait.php',
|
||||||
|
'Nyholm\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/nyholm/psr7/src/UploadedFile.php',
|
||||||
|
'Nyholm\\Psr7\\Uri' => __DIR__ . '/..' . '/nyholm/psr7/src/Uri.php',
|
||||||
'PHPUnit\\Event\\Application\\Finished' => __DIR__ . '/..' . '/phpunit/phpunit/src/Event/Events/Application/Finished.php',
|
'PHPUnit\\Event\\Application\\Finished' => __DIR__ . '/..' . '/phpunit/phpunit/src/Event/Events/Application/Finished.php',
|
||||||
'PHPUnit\\Event\\Application\\FinishedSubscriber' => __DIR__ . '/..' . '/phpunit/phpunit/src/Event/Events/Application/FinishedSubscriber.php',
|
'PHPUnit\\Event\\Application\\FinishedSubscriber' => __DIR__ . '/..' . '/phpunit/phpunit/src/Event/Events/Application/FinishedSubscriber.php',
|
||||||
'PHPUnit\\Event\\Application\\Started' => __DIR__ . '/..' . '/phpunit/phpunit/src/Event/Events/Application/Started.php',
|
'PHPUnit\\Event\\Application\\Started' => __DIR__ . '/..' . '/phpunit/phpunit/src/Event/Events/Application/Started.php',
|
||||||
@@ -7275,6 +7329,51 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'PhpParser\\PrettyPrinter\\Standard' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php',
|
'PhpParser\\PrettyPrinter\\Standard' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php',
|
||||||
'PhpParser\\Token' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Token.php',
|
'PhpParser\\Token' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Token.php',
|
||||||
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||||
|
'Phrity\\Comparison\\Comparable' => __DIR__ . '/..' . '/phrity/comparison/src/Comparable.php',
|
||||||
|
'Phrity\\Comparison\\Comparator' => __DIR__ . '/..' . '/phrity/comparison/src/Comparator.php',
|
||||||
|
'Phrity\\Comparison\\ComparisonTrait' => __DIR__ . '/..' . '/phrity/comparison/src/ComparisonTrait.php',
|
||||||
|
'Phrity\\Comparison\\Equalable' => __DIR__ . '/..' . '/phrity/comparison/src/Equalable.php',
|
||||||
|
'Phrity\\Comparison\\IncomparableException' => __DIR__ . '/..' . '/phrity/comparison/src/IncomparableException.php',
|
||||||
|
'Phrity\\Http\\HttpFactory' => __DIR__ . '/..' . '/phrity/http/src/HttpFactory.php',
|
||||||
|
'Phrity\\Http\\Serializer' => __DIR__ . '/..' . '/phrity/http/src/Serializer.php',
|
||||||
|
'Phrity\\Net\\Context' => __DIR__ . '/..' . '/phrity/net-stream/src/Context.php',
|
||||||
|
'Phrity\\Net\\SocketClient' => __DIR__ . '/..' . '/phrity/net-stream/src/SocketClient.php',
|
||||||
|
'Phrity\\Net\\SocketServer' => __DIR__ . '/..' . '/phrity/net-stream/src/SocketServer.php',
|
||||||
|
'Phrity\\Net\\SocketStream' => __DIR__ . '/..' . '/phrity/net-stream/src/SocketStream.php',
|
||||||
|
'Phrity\\Net\\Stream' => __DIR__ . '/..' . '/phrity/net-stream/src/Stream.php',
|
||||||
|
'Phrity\\Net\\StreamCollection' => __DIR__ . '/..' . '/phrity/net-stream/src/StreamCollection.php',
|
||||||
|
'Phrity\\Net\\StreamContainerInterface' => __DIR__ . '/..' . '/phrity/net-stream/src/StreamContainerInterface.php',
|
||||||
|
'Phrity\\Net\\StreamException' => __DIR__ . '/..' . '/phrity/net-stream/src/StreamException.php',
|
||||||
|
'Phrity\\Net\\StreamFactory' => __DIR__ . '/..' . '/phrity/net-stream/src/StreamFactory.php',
|
||||||
|
'Phrity\\Net\\StreamInterface' => __DIR__ . '/..' . '/phrity/net-stream/src/StreamInterface.php',
|
||||||
|
'Phrity\\Net\\Uri' => __DIR__ . '/..' . '/phrity/net-uri/src/Uri.php',
|
||||||
|
'Phrity\\Net\\UriFactory' => __DIR__ . '/..' . '/phrity/net-uri/src/UriFactory.php',
|
||||||
|
'Phrity\\Util\\Accessor' => __DIR__ . '/..' . '/phrity/util-accessor/src/Accessor.php',
|
||||||
|
'Phrity\\Util\\AccessorException' => __DIR__ . '/..' . '/phrity/util-accessor/src/AccessorException.php',
|
||||||
|
'Phrity\\Util\\AccessorTrait' => __DIR__ . '/..' . '/phrity/util-accessor/src/AccessorTrait.php',
|
||||||
|
'Phrity\\Util\\DataAccessor' => __DIR__ . '/..' . '/phrity/util-accessor/src/DataAccessor.php',
|
||||||
|
'Phrity\\Util\\ErrorHandler' => __DIR__ . '/..' . '/phrity/util-errorhandler/src/ErrorHandler.php',
|
||||||
|
'Phrity\\Util\\Interpolator\\Interpolator' => __DIR__ . '/..' . '/phrity/util-interpolator/src/Interpolator.php',
|
||||||
|
'Phrity\\Util\\Interpolator\\InterpolatorTrait' => __DIR__ . '/..' . '/phrity/util-interpolator/src/InterpolatorTrait.php',
|
||||||
|
'Phrity\\Util\\PathAccessor' => __DIR__ . '/..' . '/phrity/util-accessor/src/PathAccessor.php',
|
||||||
|
'Phrity\\Util\\Transformer\\BasicTypeConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/BasicTypeConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ChainedResolver' => __DIR__ . '/..' . '/phrity/util-transformer/src/ChainedResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\DateTimeConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/DateTimeConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\EnumConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/EnumConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\FirstMatchResolver' => __DIR__ . '/..' . '/phrity/util-transformer/src/FirstMatchResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\FlattenDecoder' => __DIR__ . '/..' . '/phrity/util-transformer/src/FlattenDecoder.php',
|
||||||
|
'Phrity\\Util\\Transformer\\JsonDecoder' => __DIR__ . '/..' . '/phrity/util-transformer/src/JsonDecoder.php',
|
||||||
|
'Phrity\\Util\\Transformer\\JsonSerializableConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/JsonSerializableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ReadableConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/ReadableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\RecursionResolver' => __DIR__ . '/..' . '/phrity/util-transformer/src/RecursionResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ReversedReadableConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/ReversedReadableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\StringResolver' => __DIR__ . '/..' . '/phrity/util-transformer/src/StringResolver.php',
|
||||||
|
'Phrity\\Util\\Transformer\\StringableConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/StringableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\Symfony\\NormalizerWrapper' => __DIR__ . '/..' . '/phrity/util-transformer/src/Symfony/NormalizerWrapper.php',
|
||||||
|
'Phrity\\Util\\Transformer\\ThrowableConverter' => __DIR__ . '/..' . '/phrity/util-transformer/src/ThrowableConverter.php',
|
||||||
|
'Phrity\\Util\\Transformer\\TransformerException' => __DIR__ . '/..' . '/phrity/util-transformer/src/TransformerException.php',
|
||||||
|
'Phrity\\Util\\Transformer\\TransformerInterface' => __DIR__ . '/..' . '/phrity/util-transformer/src/TransformerInterface.php',
|
||||||
|
'Phrity\\Util\\Transformer\\Type' => __DIR__ . '/..' . '/phrity/util-transformer/src/Type.php',
|
||||||
'Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php',
|
'Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php',
|
||||||
'Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php',
|
'Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php',
|
||||||
'Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php',
|
'Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php',
|
||||||
@@ -9573,6 +9672,8 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'Termwind\\ValueObjects\\Node' => __DIR__ . '/..' . '/nunomaduro/termwind/src/ValueObjects/Node.php',
|
'Termwind\\ValueObjects\\Node' => __DIR__ . '/..' . '/nunomaduro/termwind/src/ValueObjects/Node.php',
|
||||||
'Termwind\\ValueObjects\\Style' => __DIR__ . '/..' . '/nunomaduro/termwind/src/ValueObjects/Style.php',
|
'Termwind\\ValueObjects\\Style' => __DIR__ . '/..' . '/nunomaduro/termwind/src/ValueObjects/Style.php',
|
||||||
'Termwind\\ValueObjects\\Styles' => __DIR__ . '/..' . '/nunomaduro/termwind/src/ValueObjects/Styles.php',
|
'Termwind\\ValueObjects\\Styles' => __DIR__ . '/..' . '/nunomaduro/termwind/src/ValueObjects/Styles.php',
|
||||||
|
'Tests\\Feature\\AjaxPostEndpointsTest' => __DIR__ . '/../..' . '/tests/Feature/AjaxPostEndpointsTest.php',
|
||||||
|
'Tests\\Feature\\CsrfExceptionsTest' => __DIR__ . '/../..' . '/tests/Feature/CsrfExceptionsTest.php',
|
||||||
'Tests\\Feature\\DashTest' => __DIR__ . '/../..' . '/tests/Feature/DashTest.php',
|
'Tests\\Feature\\DashTest' => __DIR__ . '/../..' . '/tests/Feature/DashTest.php',
|
||||||
'Tests\\Feature\\GetStatsTest' => __DIR__ . '/../..' . '/tests/Feature/GetStatsTest.php',
|
'Tests\\Feature\\GetStatsTest' => __DIR__ . '/../..' . '/tests/Feature/GetStatsTest.php',
|
||||||
'Tests\\Feature\\ImportTest' => __DIR__ . '/../..' . '/tests/Feature/ImportTest.php',
|
'Tests\\Feature\\ImportTest' => __DIR__ . '/../..' . '/tests/Feature/ImportTest.php',
|
||||||
@@ -9581,16 +9682,24 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'Tests\\Feature\\ItemExportTest' => __DIR__ . '/../..' . '/tests/Feature/ItemExportTest.php',
|
'Tests\\Feature\\ItemExportTest' => __DIR__ . '/../..' . '/tests/Feature/ItemExportTest.php',
|
||||||
'Tests\\Feature\\ItemImportTest' => __DIR__ . '/../..' . '/tests/Feature/ItemImportTest.php',
|
'Tests\\Feature\\ItemImportTest' => __DIR__ . '/../..' . '/tests/Feature/ItemImportTest.php',
|
||||||
'Tests\\Feature\\ItemListTest' => __DIR__ . '/../..' . '/tests/Feature/ItemListTest.php',
|
'Tests\\Feature\\ItemListTest' => __DIR__ . '/../..' . '/tests/Feature/ItemListTest.php',
|
||||||
|
'Tests\\Feature\\ItemOwnershipTest' => __DIR__ . '/../..' . '/tests/Feature/ItemOwnershipTest.php',
|
||||||
|
'Tests\\Feature\\OrphanItemRecoveryTest' => __DIR__ . '/../..' . '/tests/Feature/OrphanItemRecoveryTest.php',
|
||||||
|
'Tests\\Feature\\RoutesRenderTest' => __DIR__ . '/../..' . '/tests/Feature/RoutesRenderTest.php',
|
||||||
'Tests\\Feature\\SearchTest' => __DIR__ . '/../..' . '/tests/Feature/SearchTest.php',
|
'Tests\\Feature\\SearchTest' => __DIR__ . '/../..' . '/tests/Feature/SearchTest.php',
|
||||||
'Tests\\Feature\\SettingsTest' => __DIR__ . '/../..' . '/tests/Feature/SettingsTest.php',
|
'Tests\\Feature\\SettingsTest' => __DIR__ . '/../..' . '/tests/Feature/SettingsTest.php',
|
||||||
|
'Tests\\Feature\\StorageDiskTest' => __DIR__ . '/../..' . '/tests/Feature/StorageDiskTest.php',
|
||||||
'Tests\\Feature\\TagListTest' => __DIR__ . '/../..' . '/tests/Feature/TagListTest.php',
|
'Tests\\Feature\\TagListTest' => __DIR__ . '/../..' . '/tests/Feature/TagListTest.php',
|
||||||
'Tests\\Feature\\TrustHostsTest' => __DIR__ . '/../..' . '/tests/Feature/TrustHostsTest.php',
|
'Tests\\Feature\\TrustHostsTest' => __DIR__ . '/../..' . '/tests/Feature/TrustHostsTest.php',
|
||||||
'Tests\\Feature\\TrustProxiesTest' => __DIR__ . '/../..' . '/tests/Feature/TrustProxiesTest.php',
|
'Tests\\Feature\\TrustProxiesTest' => __DIR__ . '/../..' . '/tests/Feature/TrustProxiesTest.php',
|
||||||
|
'Tests\\Feature\\UserDeleteItemsTest' => __DIR__ . '/../..' . '/tests/Feature/UserDeleteItemsTest.php',
|
||||||
'Tests\\Feature\\UserEditTest' => __DIR__ . '/../..' . '/tests/Feature/UserEditTest.php',
|
'Tests\\Feature\\UserEditTest' => __DIR__ . '/../..' . '/tests/Feature/UserEditTest.php',
|
||||||
'Tests\\Feature\\UserListTest' => __DIR__ . '/../..' . '/tests/Feature/UserListTest.php',
|
'Tests\\Feature\\UserListTest' => __DIR__ . '/../..' . '/tests/Feature/UserListTest.php',
|
||||||
'Tests\\TestCase' => __DIR__ . '/../..' . '/tests/TestCase.php',
|
'Tests\\TestCase' => __DIR__ . '/../..' . '/tests/TestCase.php',
|
||||||
'Tests\\Unit\\database\\seeders\\SettingsSeederTest' => __DIR__ . '/../..' . '/tests/Unit/database/seeders/SettingsSeederTest.php',
|
'Tests\\Unit\\database\\seeders\\SettingsSeederTest' => __DIR__ . '/../..' . '/tests/Unit/database/seeders/SettingsSeederTest.php',
|
||||||
|
'Tests\\Unit\\helpers\\ClassNameTest' => __DIR__ . '/../..' . '/tests/Unit/helpers/ClassNameTest.php',
|
||||||
|
'Tests\\Unit\\helpers\\ColorHelpersTest' => __DIR__ . '/../..' . '/tests/Unit/helpers/ColorHelpersTest.php',
|
||||||
'Tests\\Unit\\helpers\\IsImageTest' => __DIR__ . '/../..' . '/tests/Unit/helpers/IsImageTest.php',
|
'Tests\\Unit\\helpers\\IsImageTest' => __DIR__ . '/../..' . '/tests/Unit/helpers/IsImageTest.php',
|
||||||
|
'Tests\\Unit\\helpers\\SizeHelpersTest' => __DIR__ . '/../..' . '/tests/Unit/helpers/SizeHelpersTest.php',
|
||||||
'Tests\\Unit\\helpers\\SlugTest' => __DIR__ . '/../..' . '/tests/Unit/helpers/SlugTest.php',
|
'Tests\\Unit\\helpers\\SlugTest' => __DIR__ . '/../..' . '/tests/Unit/helpers/SlugTest.php',
|
||||||
'Tests\\Unit\\lang\\LangTest' => __DIR__ . '/../..' . '/tests/Unit/lang/LangTest.php',
|
'Tests\\Unit\\lang\\LangTest' => __DIR__ . '/../..' . '/tests/Unit/lang/LangTest.php',
|
||||||
'TheSeer\\Tokenizer\\Exception' => __DIR__ . '/..' . '/theseer/tokenizer/src/Exception.php',
|
'TheSeer\\Tokenizer\\Exception' => __DIR__ . '/..' . '/theseer/tokenizer/src/Exception.php',
|
||||||
@@ -9609,6 +9718,66 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
|
|||||||
'TijsVerkoyen\\CssToInlineStyles\\Css\\Rule\\Rule' => __DIR__ . '/..' . '/tijsverkoyen/css-to-inline-styles/src/Css/Rule/Rule.php',
|
'TijsVerkoyen\\CssToInlineStyles\\Css\\Rule\\Rule' => __DIR__ . '/..' . '/tijsverkoyen/css-to-inline-styles/src/Css/Rule/Rule.php',
|
||||||
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||||
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
||||||
|
'WebSocket\\Client' => __DIR__ . '/..' . '/phrity/websocket/src/Client.php',
|
||||||
|
'WebSocket\\Configuration' => __DIR__ . '/..' . '/phrity/websocket/src/Configuration.php',
|
||||||
|
'WebSocket\\Connection' => __DIR__ . '/..' . '/phrity/websocket/src/Connection.php',
|
||||||
|
'WebSocket\\Constant' => __DIR__ . '/..' . '/phrity/websocket/src/Constant.php',
|
||||||
|
'WebSocket\\Exception\\BadOpcodeException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/BadOpcodeException.php',
|
||||||
|
'WebSocket\\Exception\\BadUriException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/BadUriException.php',
|
||||||
|
'WebSocket\\Exception\\ClientException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ClientException.php',
|
||||||
|
'WebSocket\\Exception\\CloseException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/CloseException.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionClosedException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ConnectionClosedException.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionFailureException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ConnectionFailureException.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionLevelInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ConnectionLevelInterface.php',
|
||||||
|
'WebSocket\\Exception\\ConnectionTimeoutException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ConnectionTimeoutException.php',
|
||||||
|
'WebSocket\\Exception\\Exception' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/Exception.php',
|
||||||
|
'WebSocket\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ExceptionInterface.php',
|
||||||
|
'WebSocket\\Exception\\HandshakeException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/HandshakeException.php',
|
||||||
|
'WebSocket\\Exception\\MessageLevelInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/MessageLevelInterface.php',
|
||||||
|
'WebSocket\\Exception\\ReconnectException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ReconnectException.php',
|
||||||
|
'WebSocket\\Exception\\RunnerException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/RunnerException.php',
|
||||||
|
'WebSocket\\Exception\\ServerException' => __DIR__ . '/..' . '/phrity/websocket/src/Exception/ServerException.php',
|
||||||
|
'WebSocket\\Frame\\Frame' => __DIR__ . '/..' . '/phrity/websocket/src/Frame/Frame.php',
|
||||||
|
'WebSocket\\Frame\\FrameHandler' => __DIR__ . '/..' . '/phrity/websocket/src/Frame/FrameHandler.php',
|
||||||
|
'WebSocket\\Http\\DefaultHttpFactory' => __DIR__ . '/..' . '/phrity/websocket/src/Http/DefaultHttpFactory.php',
|
||||||
|
'WebSocket\\Http\\HttpHandler' => __DIR__ . '/..' . '/phrity/websocket/src/Http/HttpHandler.php',
|
||||||
|
'WebSocket\\Http\\Request' => __DIR__ . '/..' . '/phrity/websocket/src/Http/Request.php',
|
||||||
|
'WebSocket\\Http\\Response' => __DIR__ . '/..' . '/phrity/websocket/src/Http/Response.php',
|
||||||
|
'WebSocket\\Http\\ServerRequest' => __DIR__ . '/..' . '/phrity/websocket/src/Http/ServerRequest.php',
|
||||||
|
'WebSocket\\Message\\Binary' => __DIR__ . '/..' . '/phrity/websocket/src/Message/Binary.php',
|
||||||
|
'WebSocket\\Message\\Close' => __DIR__ . '/..' . '/phrity/websocket/src/Message/Close.php',
|
||||||
|
'WebSocket\\Message\\Message' => __DIR__ . '/..' . '/phrity/websocket/src/Message/Message.php',
|
||||||
|
'WebSocket\\Message\\MessageHandler' => __DIR__ . '/..' . '/phrity/websocket/src/Message/MessageHandler.php',
|
||||||
|
'WebSocket\\Message\\Ping' => __DIR__ . '/..' . '/phrity/websocket/src/Message/Ping.php',
|
||||||
|
'WebSocket\\Message\\Pong' => __DIR__ . '/..' . '/phrity/websocket/src/Message/Pong.php',
|
||||||
|
'WebSocket\\Message\\Text' => __DIR__ . '/..' . '/phrity/websocket/src/Message/Text.php',
|
||||||
|
'WebSocket\\Middleware\\Callback' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/Callback.php',
|
||||||
|
'WebSocket\\Middleware\\CloseHandler' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/CloseHandler.php',
|
||||||
|
'WebSocket\\Middleware\\CompressionExtension' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/CompressionExtension.php',
|
||||||
|
'WebSocket\\Middleware\\CompressionExtension\\CompressorInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/CompressionExtension/CompressorInterface.php',
|
||||||
|
'WebSocket\\Middleware\\CompressionExtension\\DeflateCompressor' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/CompressionExtension/DeflateCompressor.php',
|
||||||
|
'WebSocket\\Middleware\\FollowRedirect' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/FollowRedirect.php',
|
||||||
|
'WebSocket\\Middleware\\MiddlewareHandler' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/MiddlewareHandler.php',
|
||||||
|
'WebSocket\\Middleware\\MiddlewareInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/MiddlewareInterface.php',
|
||||||
|
'WebSocket\\Middleware\\PingInterval' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/PingInterval.php',
|
||||||
|
'WebSocket\\Middleware\\PingResponder' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/PingResponder.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessHttpIncomingInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessHttpIncomingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessHttpOutgoingInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessHttpOutgoingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessHttpStack' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessHttpStack.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessIncomingInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessIncomingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessOutgoingInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessOutgoingInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessStack' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessStack.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessTickInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessTickInterface.php',
|
||||||
|
'WebSocket\\Middleware\\ProcessTickStack' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/ProcessTickStack.php',
|
||||||
|
'WebSocket\\Middleware\\SubprotocolNegotiation' => __DIR__ . '/..' . '/phrity/websocket/src/Middleware/SubprotocolNegotiation.php',
|
||||||
|
'WebSocket\\Runtime\\IdentityInterface' => __DIR__ . '/..' . '/phrity/websocket/src/Runtime/IdentityInterface.php',
|
||||||
|
'WebSocket\\Runtime\\Runner' => __DIR__ . '/..' . '/phrity/websocket/src/Runtime/Runner.php',
|
||||||
|
'WebSocket\\Server' => __DIR__ . '/..' . '/phrity/websocket/src/Server.php',
|
||||||
|
'WebSocket\\Trait\\ConfigurationTrait' => __DIR__ . '/..' . '/phrity/websocket/src/Trait/ConfigurationTrait.php',
|
||||||
|
'WebSocket\\Trait\\ListenerTrait' => __DIR__ . '/..' . '/phrity/websocket/src/Trait/ListenerTrait.php',
|
||||||
|
'WebSocket\\Trait\\OpcodeTrait' => __DIR__ . '/..' . '/phrity/websocket/src/Trait/OpcodeTrait.php',
|
||||||
|
'WebSocket\\Trait\\SendMethodsTrait' => __DIR__ . '/..' . '/phrity/websocket/src/Trait/SendMethodsTrait.php',
|
||||||
|
'WebSocket\\Trait\\StringableTrait' => __DIR__ . '/..' . '/phrity/websocket/src/Trait/StringableTrait.php',
|
||||||
'Whoops\\Exception\\ErrorException' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Exception/ErrorException.php',
|
'Whoops\\Exception\\ErrorException' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Exception/ErrorException.php',
|
||||||
'Whoops\\Exception\\Formatter' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Exception/Formatter.php',
|
'Whoops\\Exception\\Formatter' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Exception/Formatter.php',
|
||||||
'Whoops\\Exception\\Frame' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Exception/Frame.php',
|
'Whoops\\Exception\\Frame' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Exception/Frame.php',
|
||||||
|
|||||||
Vendored
+628
@@ -4235,6 +4235,87 @@
|
|||||||
],
|
],
|
||||||
"install-path": "../nunomaduro/termwind"
|
"install-path": "../nunomaduro/termwind"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "nyholm/psr7",
|
||||||
|
"version": "1.8.2",
|
||||||
|
"version_normalized": "1.8.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Nyholm/psr7.git",
|
||||||
|
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
|
||||||
|
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"php-http/message-factory-implementation": "1.0",
|
||||||
|
"psr/http-factory-implementation": "1.0",
|
||||||
|
"psr/http-message-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"http-interop/http-factory-tests": "^0.9",
|
||||||
|
"php-http/message-factory": "^1.0",
|
||||||
|
"php-http/psr7-integration-tests": "^1.0",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
|
||||||
|
"symfony/error-handler": "^4.4"
|
||||||
|
},
|
||||||
|
"time": "2024-09-09T07:06:30+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.8-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nyholm\\Psr7\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Martijn van der Ven",
|
||||||
|
"email": "martijn@vanderven.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A fast PHP7 implementation of PSR-7",
|
||||||
|
"homepage": "https://tnyholm.se",
|
||||||
|
"keywords": [
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Nyholm/psr7/issues",
|
||||||
|
"source": "https://github.com/Nyholm/psr7/tree/1.8.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Zegnat",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nyholm",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../nyholm/psr7"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phar-io/manifest",
|
"name": "phar-io/manifest",
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
@@ -5346,6 +5427,553 @@
|
|||||||
],
|
],
|
||||||
"install-path": "../phpunit/phpunit"
|
"install-path": "../phpunit/phpunit"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/comparison",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"version_normalized": "1.4.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-comparison.git",
|
||||||
|
"reference": "cf80abb822537eeaaeb4142157cd667ca6372a13"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-comparison/zipball/cf80abb822537eeaaeb4142157cd667ca6372a13",
|
||||||
|
"reference": "cf80abb822537eeaaeb4142157cd667ca6372a13",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T07:38:30+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Comparison\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Interfaces and helper trait for comparing objects. Comparator for sort and filter applications.",
|
||||||
|
"homepage": "https://phrity.sirn.se/comparison",
|
||||||
|
"keywords": [
|
||||||
|
"comparable",
|
||||||
|
"comparator",
|
||||||
|
"comparison",
|
||||||
|
"equalable",
|
||||||
|
"filter",
|
||||||
|
"sort"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-comparison/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-comparison/tree/1.4.1"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/comparison"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/http",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"version_normalized": "1.1.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-http.git",
|
||||||
|
"reference": "1e7eee67359287b94aae2b7d40b730d5f5394943"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-http/zipball/1e7eee67359287b94aae2b7d40b730d5f5394943",
|
||||||
|
"reference": "1e7eee67359287b94aae2b7d40b730d5f5394943",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/psr7": "^2.0",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"time": "2025-12-22T20:22:29+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Http\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Utilities and interfaces for handling HTTP.",
|
||||||
|
"homepage": "https://phrity.sirn.se/http",
|
||||||
|
"keywords": [
|
||||||
|
"HTTP Factories",
|
||||||
|
"HTTP Serializer",
|
||||||
|
"http",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-http/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-http/tree/1.1.0"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/http"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/net-stream",
|
||||||
|
"version": "2.4.0",
|
||||||
|
"version_normalized": "2.4.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-net-stream.git",
|
||||||
|
"reference": "a0726a8dddf11a4cd3a9e45d17e145d78e948f43"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-net-stream/zipball/a0726a8dddf11a4cd3a9e45d17e145d78e948f43",
|
||||||
|
"reference": "a0726a8dddf11a4cd3a9e45d17e145d78e948f43",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"phrity/util-interpolator": "^1.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phrity/net-uri": "^2.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^4.0"
|
||||||
|
},
|
||||||
|
"time": "2026-03-25T18:16:40+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Net\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Socket stream classes implementing PSR-7 Stream and PSR-17 StreamFactory",
|
||||||
|
"homepage": "https://phrity.sirn.se/net-stream",
|
||||||
|
"keywords": [
|
||||||
|
"Socket",
|
||||||
|
"client",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7",
|
||||||
|
"server",
|
||||||
|
"stream",
|
||||||
|
"stream factory"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-net-stream/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-net-stream/tree/2.4.0"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/net-stream"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/net-uri",
|
||||||
|
"version": "2.2.1",
|
||||||
|
"version_normalized": "2.2.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-net-uri.git",
|
||||||
|
"reference": "0737de026b75177ae302ac9fdbbd0ffc2610f3b8"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-net-uri/zipball/0737de026b75177ae302ac9fdbbd0ffc2610f3b8",
|
||||||
|
"reference": "0737de026b75177ae302ac9fdbbd0ffc2610f3b8",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/comparison": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 | ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "Enables IDN conversion for non-ASCII domains"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T10:39:22+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Net\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PSR-7 Uri and PSR-17 UriFactory implementation",
|
||||||
|
"homepage": "https://phrity.sirn.se/net-uri",
|
||||||
|
"keywords": [
|
||||||
|
"psr-17",
|
||||||
|
"psr-7",
|
||||||
|
"uri",
|
||||||
|
"uri factory"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-net-uri/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-net-uri/tree/2.2.1"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/net-uri"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-accessor",
|
||||||
|
"version": "1.3.1",
|
||||||
|
"version_normalized": "1.3.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-accessor.git",
|
||||||
|
"reference": "e9741da6b532fa2da7f627fce60fa77f00f6e354"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-accessor/zipball/e9741da6b532fa2da7f627fce60fa77f00f6e354",
|
||||||
|
"reference": "e9741da6b532fa2da7f627fce60fa77f00f6e354",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-transformer": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T21:18:15+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Utility to handle access to a data set by using access paths. Similar to and partly compatible with xpath and json-pointer.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-accessor",
|
||||||
|
"keywords": [
|
||||||
|
"accessor"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-accessor/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-accessor/tree/1.3.1"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/util-accessor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-errorhandler",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"version_normalized": "1.2.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-errorhandler.git",
|
||||||
|
"reference": "70a669cc22db2eed6a109ec66fd95168a4332c9b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/70a669cc22db2eed6a109ec66fd95168a4332c9b",
|
||||||
|
"reference": "70a669cc22db2eed6a109ec66fd95168a4332c9b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T21:25:36+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Inline error handler; catch and resolve errors for code block.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-errorhandler",
|
||||||
|
"keywords": [
|
||||||
|
"error",
|
||||||
|
"warning"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-errorhandler/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.2.2"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/util-errorhandler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-interpolator",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"version_normalized": "1.1.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-interpolator.git",
|
||||||
|
"reference": "18b0362d2aff60984c530808333c5e64c80c3e50"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-interpolator/zipball/18b0362d2aff60984c530808333c5e64c80c3e50",
|
||||||
|
"reference": "18b0362d2aff60984c530808333c5e64c80c3e50",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-accessor": "^1.3",
|
||||||
|
"phrity/util-transformer": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T21:35:25+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Interpolator\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Interpolator class and trait.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-interpolator",
|
||||||
|
"keywords": [
|
||||||
|
"interpolate",
|
||||||
|
"interpolator"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-interpolator/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-interpolator/tree/1.1.1"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/util-interpolator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/util-transformer",
|
||||||
|
"version": "1.3.1",
|
||||||
|
"version_normalized": "1.3.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/phrity-util-transformer.git",
|
||||||
|
"reference": "3fd4d7fa4077deafe5130c4b9ee8146b5488abe3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/phrity-util-transformer/zipball/3fd4d7fa4077deafe5130c4b9ee8146b5488abe3",
|
||||||
|
"reference": "3fd4d7fa4077deafe5130c4b9ee8146b5488abe3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0",
|
||||||
|
"symfony/property-access": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/serializer": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/translation-contracts": "^3.0",
|
||||||
|
"symfony/uid": "^6.0 || ^7.0 || ^8.0"
|
||||||
|
},
|
||||||
|
"time": "2025-12-05T22:07:04+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Transformer\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Type transformers, normalizers and resolvers.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-transformer",
|
||||||
|
"keywords": [
|
||||||
|
"normalizer",
|
||||||
|
"resolver",
|
||||||
|
"transformer",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/phrity-util-transformer/issues",
|
||||||
|
"source": "https://github.com/sirn-se/phrity-util-transformer/tree/1.3.1"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/util-transformer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "phrity/websocket",
|
||||||
|
"version": "3.7.3",
|
||||||
|
"version_normalized": "3.7.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sirn-se/websocket-php.git",
|
||||||
|
"reference": "c92d613ec298ea108b8ebae306609cd40427f7d6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sirn-se/websocket-php/zipball/c92d613ec298ea108b8ebae306609cd40427f7d6",
|
||||||
|
"reference": "c92d613ec298ea108b8ebae306609cd40427f7d6",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"nyholm/psr7": "^1.4",
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/http": "^1.1",
|
||||||
|
"phrity/net-stream": "^2.4",
|
||||||
|
"phrity/net-uri": "^2.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0",
|
||||||
|
"psr/log": "^1.0 || ^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phrity/logger-console": "^1.0",
|
||||||
|
"phrity/net-mock": "^2.4",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^4.0"
|
||||||
|
},
|
||||||
|
"time": "2026-06-22T14:53:45+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"WebSocket\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"ISC"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fredrik Liljegren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "WebSocket client and server",
|
||||||
|
"homepage": "https://phrity.sirn.se/websocket",
|
||||||
|
"keywords": [
|
||||||
|
"client",
|
||||||
|
"server",
|
||||||
|
"websocket"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sirn-se/websocket-php/issues",
|
||||||
|
"source": "https://github.com/sirn-se/websocket-php/tree/3.7.3"
|
||||||
|
},
|
||||||
|
"install-path": "../phrity/websocket"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/cache",
|
"name": "psr/cache",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|||||||
Vendored
+92
-2
@@ -3,7 +3,7 @@
|
|||||||
'name' => 'laravel/laravel',
|
'name' => 'laravel/laravel',
|
||||||
'pretty_version' => '2.x-dev',
|
'pretty_version' => '2.x-dev',
|
||||||
'version' => '2.9999999.9999999.9999999-dev',
|
'version' => '2.9999999.9999999.9999999-dev',
|
||||||
'reference' => '39191885e53b2c8ab49b3d594ece9825b6fd87b4',
|
'reference' => '93a321ff5b77e698e52d0e204487a968df4b5fc9',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
@@ -508,7 +508,7 @@
|
|||||||
'laravel/laravel' => array(
|
'laravel/laravel' => array(
|
||||||
'pretty_version' => '2.x-dev',
|
'pretty_version' => '2.x-dev',
|
||||||
'version' => '2.9999999.9999999.9999999-dev',
|
'version' => '2.9999999.9999999.9999999-dev',
|
||||||
'reference' => '39191885e53b2c8ab49b3d594ece9825b6fd87b4',
|
'reference' => '93a321ff5b77e698e52d0e204487a968df4b5fc9',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
@@ -727,6 +727,15 @@
|
|||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'nyholm/psr7' => array(
|
||||||
|
'pretty_version' => '1.8.2',
|
||||||
|
'version' => '1.8.2.0',
|
||||||
|
'reference' => 'a71f2b11690f4b24d099d6b16690a90ae14fc6f3',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../nyholm/psr7',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'phar-io/manifest' => array(
|
'phar-io/manifest' => array(
|
||||||
'pretty_version' => '2.0.4',
|
'pretty_version' => '2.0.4',
|
||||||
'version' => '2.0.4.0',
|
'version' => '2.0.4.0',
|
||||||
@@ -889,6 +898,87 @@
|
|||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => true,
|
'dev_requirement' => true,
|
||||||
),
|
),
|
||||||
|
'phrity/comparison' => array(
|
||||||
|
'pretty_version' => '1.4.1',
|
||||||
|
'version' => '1.4.1.0',
|
||||||
|
'reference' => 'cf80abb822537eeaaeb4142157cd667ca6372a13',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/comparison',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/http' => array(
|
||||||
|
'pretty_version' => '1.1.0',
|
||||||
|
'version' => '1.1.0.0',
|
||||||
|
'reference' => '1e7eee67359287b94aae2b7d40b730d5f5394943',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/http',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/net-stream' => array(
|
||||||
|
'pretty_version' => '2.4.0',
|
||||||
|
'version' => '2.4.0.0',
|
||||||
|
'reference' => 'a0726a8dddf11a4cd3a9e45d17e145d78e948f43',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/net-stream',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/net-uri' => array(
|
||||||
|
'pretty_version' => '2.2.1',
|
||||||
|
'version' => '2.2.1.0',
|
||||||
|
'reference' => '0737de026b75177ae302ac9fdbbd0ffc2610f3b8',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/net-uri',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/util-accessor' => array(
|
||||||
|
'pretty_version' => '1.3.1',
|
||||||
|
'version' => '1.3.1.0',
|
||||||
|
'reference' => 'e9741da6b532fa2da7f627fce60fa77f00f6e354',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/util-accessor',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/util-errorhandler' => array(
|
||||||
|
'pretty_version' => '1.2.2',
|
||||||
|
'version' => '1.2.2.0',
|
||||||
|
'reference' => '70a669cc22db2eed6a109ec66fd95168a4332c9b',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/util-errorhandler',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/util-interpolator' => array(
|
||||||
|
'pretty_version' => '1.1.1',
|
||||||
|
'version' => '1.1.1.0',
|
||||||
|
'reference' => '18b0362d2aff60984c530808333c5e64c80c3e50',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/util-interpolator',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/util-transformer' => array(
|
||||||
|
'pretty_version' => '1.3.1',
|
||||||
|
'version' => '1.3.1.0',
|
||||||
|
'reference' => '3fd4d7fa4077deafe5130c4b9ee8146b5488abe3',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/util-transformer',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'phrity/websocket' => array(
|
||||||
|
'pretty_version' => '3.7.3',
|
||||||
|
'version' => '3.7.3.0',
|
||||||
|
'reference' => 'c92d613ec298ea108b8ebae306609cd40427f7d6',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../phrity/websocket',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'psr/cache' => array(
|
'psr/cache' => array(
|
||||||
'pretty_version' => '3.0.0',
|
'pretty_version' => '3.0.0',
|
||||||
'version' => '3.0.0.0',
|
'version' => '3.0.0.0',
|
||||||
|
|||||||
Vendored
+172
@@ -0,0 +1,172 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file, in reverse chronological order by release.
|
||||||
|
|
||||||
|
## 1.8.2
|
||||||
|
|
||||||
|
- Fix deprecation warnings in PHP 8.4
|
||||||
|
|
||||||
|
## 1.8.1
|
||||||
|
|
||||||
|
- Fix error handling in Stream::getContents()
|
||||||
|
|
||||||
|
## 1.8.0
|
||||||
|
|
||||||
|
- Deprecate HttplugFactory, use Psr17Factory instead
|
||||||
|
- Make depencendy on php-http/message-factory optional
|
||||||
|
|
||||||
|
## 1.7.0
|
||||||
|
|
||||||
|
- Bump to PHP 7.2 minimum
|
||||||
|
- Allow psr/http-message v2
|
||||||
|
- Use copy-on-write for streams created from strings
|
||||||
|
|
||||||
|
## 1.6.1
|
||||||
|
|
||||||
|
- Security fix: CVE-2023-29197
|
||||||
|
|
||||||
|
## 1.6.0
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Seek to the begining of the string when using Stream::create()
|
||||||
|
- Populate ServerRequest::getQueryParams() on instantiation
|
||||||
|
- Encode [reserved characters](https://www.rfc-editor.org/rfc/rfc3986#appendix-A) in userinfo in Uri
|
||||||
|
- Normalize leading slashes for Uri::getPath()
|
||||||
|
- Make Stream's constructor public
|
||||||
|
- Add some missing type checks on arguments
|
||||||
|
|
||||||
|
## 1.5.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed deprecations on PHP 8.1
|
||||||
|
|
||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add explicit `@return mixed`
|
||||||
|
- Add explicit return types to HttplugFactory
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Improve error handling with streams
|
||||||
|
|
||||||
|
## 1.4.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `Psr17Factory::createStreamFromFile`, `UploadedFile::moveTo`, and
|
||||||
|
`UploadedFile::getStream` no longer throw `ValueError` in PHP 8.
|
||||||
|
|
||||||
|
## 1.4.0
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
The `final` keyword was replaced by `@final` annotation.
|
||||||
|
|
||||||
|
## 1.3.2
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `Stream::read()` must not return boolean.
|
||||||
|
- Improved exception message when using wrong HTTP status code.
|
||||||
|
|
||||||
|
## 1.3.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Allow installation on PHP8
|
||||||
|
|
||||||
|
## 1.3.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Make Stream::__toString() compatible with throwing exceptions on PHP 7.4.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Support for UTF-8 hostnames
|
||||||
|
- Support for numeric header names
|
||||||
|
|
||||||
|
## 1.2.1
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added `.github` and `phpstan.neon.dist` to `.gitattributes`.
|
||||||
|
|
||||||
|
## 1.2.0
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change minimal port number to 0 (unix socket)
|
||||||
|
- Updated `Psr17Factory::createResponse` to respect the specification. If second
|
||||||
|
argument is not used, a standard reason phrase. If an empty string is passed,
|
||||||
|
then the reason phrase will be empty.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Check for seekable on the stream resource.
|
||||||
|
- Fixed the `Response::$reason` should never be null.
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Improved performance
|
||||||
|
- More tests for `UploadedFile` and `HttplugFactory`
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Dead code
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Handle `fopen` failing in createStreamFromFile according to PSR-7.
|
||||||
|
- Reduce execution path to speed up performance.
|
||||||
|
- Fixed typos.
|
||||||
|
- Code style.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for final PSR-17 (HTTP factories). (`Psr17Factory`)
|
||||||
|
- Support for numeric header values.
|
||||||
|
- Support for empty header values.
|
||||||
|
- All classes are final
|
||||||
|
- `HttplugFactory` that implements factory interfaces from HTTPlug.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `ServerRequest` does not extend `Request`.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The HTTPlug discovery strategy was removed since it is included in php-http/discovery 1.4.
|
||||||
|
- `UploadedFileFactory()` was removed in favor for `Psr17Factory`.
|
||||||
|
- `ServerRequestFactory()` was removed in favor for `Psr17Factory`.
|
||||||
|
- `StreamFactory`, `UriFactory`, abd `MessageFactory`. Use `HttplugFactory` instead.
|
||||||
|
- `ServerRequestFactory::createServerRequestFromArray`, `ServerRequestFactory::createServerRequestFromArrays` and
|
||||||
|
`ServerRequestFactory::createServerRequestFromGlobals`. Please use the new `nyholm/psr7-server` instead.
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Return types.
|
||||||
|
- Many `InvalidArgumentException`s are thrown when you use invalid arguments.
|
||||||
|
- Integration tests for `UploadedFile` and `ServerRequest`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- We dropped PHP7.0 support.
|
||||||
|
- PSR-17 factories have been marked as internal. They do not fall under our BC promise until PSR-17 is accepted.
|
||||||
|
- `UploadedFileFactory::createUploadedFile` does not accept a string file path.
|
||||||
|
|
||||||
|
## 0.2.3
|
||||||
|
|
||||||
|
No changelog before this release
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Tobias Nyholm
|
||||||
|
|
||||||
|
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.
|
||||||
Vendored
+108
@@ -0,0 +1,108 @@
|
|||||||
|
# PSR-7 implementation
|
||||||
|
|
||||||
|
[](https://github.com/Nyholm/psr7/releases)
|
||||||
|
[](https://packagist.org/packages/nyholm/psr7)
|
||||||
|
[](https://packagist.org/packages/nyholm/psr7)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://github.com/Nyholm/psr7/actions/workflows/static.yml?query=branch%3Amaster)
|
||||||
|
[](https://github.com/Nyholm/psr7/actions/workflows/tests.yml?query=branch%3Amaster)
|
||||||
|
|
||||||
|
A super lightweight PSR-7 implementation. Very strict and very fast.
|
||||||
|
|
||||||
|
| Description | Guzzle | Laminas | Slim | Nyholm |
|
||||||
|
| ---- | ------ | ---- | ---- | ------ |
|
||||||
|
| Lines of code | 3.300 | 3.100 | 1.900 | 1.000 |
|
||||||
|
| PSR-7* | 66% | 100% | 75% | 100% |
|
||||||
|
| PSR-17 | No | Yes | Yes | Yes |
|
||||||
|
| HTTPlug | No | No | No | Yes |
|
||||||
|
| Performance (runs per second)** | 14.553 | 14.703 | 13.416 | 17.734 |
|
||||||
|
|
||||||
|
\* Percent of completed tests in https://github.com/php-http/psr7-integration-tests
|
||||||
|
|
||||||
|
\** Benchmark with 50.000 runs. See https://github.com/devanych/psr-http-benchmark (higher is better)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require nyholm/psr7
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using Symfony Flex then you get all message factories registered as services.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The PSR-7 objects do not contain any other public methods than those defined in
|
||||||
|
the [PSR-7 specification](https://www.php-fig.org/psr/psr-7/).
|
||||||
|
|
||||||
|
### Create objects
|
||||||
|
|
||||||
|
Use the PSR-17 factory to create requests, streams, URIs etc.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
||||||
|
$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se');
|
||||||
|
$stream = $psr17Factory->createStream('foobar');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending a request
|
||||||
|
|
||||||
|
With [HTTPlug](http://httplug.io/) or any other PSR-18 (HTTP client) you may send
|
||||||
|
requests like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require kriswallsmith/buzz
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
||||||
|
$psr18Client = new \Buzz\Client\Curl($psr17Factory);
|
||||||
|
|
||||||
|
$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se');
|
||||||
|
$response = $psr18Client->sendRequest($request);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create server requests
|
||||||
|
|
||||||
|
The [`nyholm/psr7-server`](https://github.com/Nyholm/psr7-server) package can be used
|
||||||
|
to create server requests from PHP superglobals.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require nyholm/psr7-server
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
||||||
|
|
||||||
|
$creator = new \Nyholm\Psr7Server\ServerRequestCreator(
|
||||||
|
$psr17Factory, // ServerRequestFactory
|
||||||
|
$psr17Factory, // UriFactory
|
||||||
|
$psr17Factory, // UploadedFileFactory
|
||||||
|
$psr17Factory // StreamFactory
|
||||||
|
);
|
||||||
|
|
||||||
|
$serverRequest = $creator->fromGlobals();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emitting a response
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require laminas/laminas-httphandlerrunner
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
||||||
|
|
||||||
|
$responseBody = $psr17Factory->createStream('Hello world');
|
||||||
|
$response = $psr17Factory->createResponse(200)->withBody($responseBody);
|
||||||
|
(new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter())->emit($response);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Our goal
|
||||||
|
|
||||||
|
This package is currently maintained by [Tobias Nyholm](http://nyholm.se) and
|
||||||
|
[Martijn van der Ven](https://vanderven.se/martijn/). They have decided that the
|
||||||
|
goal of this library should be to provide a super strict implementation of
|
||||||
|
[PSR-7](https://www.php-fig.org/psr/psr-7/) that is blazing fast.
|
||||||
|
|
||||||
|
The package will never include any extra features nor helper methods. All our classes
|
||||||
|
and functions exist because they are required to fulfill the PSR-7 specification.
|
||||||
Vendored
+49
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "nyholm/psr7",
|
||||||
|
"description": "A fast PHP7 implementation of PSR-7",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["psr-7", "psr-17"],
|
||||||
|
"homepage": "https://tnyholm.se",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Martijn van der Ven",
|
||||||
|
"email": "martijn@vanderven.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0",
|
||||||
|
"psr/http-factory": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
|
||||||
|
"php-http/message-factory": "^1.0",
|
||||||
|
"php-http/psr7-integration-tests": "^1.0",
|
||||||
|
"http-interop/http-factory-tests": "^0.9",
|
||||||
|
"symfony/error-handler": "^4.4"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"php-http/message-factory-implementation": "1.0",
|
||||||
|
"psr/http-message-implementation": "1.0",
|
||||||
|
"psr/http-factory-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nyholm\\Psr7\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\Nyholm\\Psr7\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.8-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7\Factory;
|
||||||
|
|
||||||
|
use Http\Message\{MessageFactory, StreamFactory, UriFactory};
|
||||||
|
use Nyholm\Psr7\{Request, Response, Stream, Uri};
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
|
|
||||||
|
if (!\interface_exists(MessageFactory::class)) {
|
||||||
|
throw new \LogicException('You cannot use "Nyholm\Psr7\Factory\HttplugFactory" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory". Note that this package is deprecated, use "psr/http-factory" instead');
|
||||||
|
}
|
||||||
|
|
||||||
|
@\trigger_error('Class "Nyholm\Psr7\Factory\HttplugFactory" is deprecated since version 1.8, use "Nyholm\Psr7\Factory\Psr17Factory" instead.', \E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*
|
||||||
|
* @deprecated since version 1.8, use Psr17Factory instead
|
||||||
|
*/
|
||||||
|
class HttplugFactory implements MessageFactory, StreamFactory, UriFactory
|
||||||
|
{
|
||||||
|
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
|
||||||
|
{
|
||||||
|
return new Request($method, $uri, $headers, $body, $protocolVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1'): ResponseInterface
|
||||||
|
{
|
||||||
|
return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createStream($body = null): StreamInterface
|
||||||
|
{
|
||||||
|
return Stream::create($body ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createUri($uri = ''): UriInterface
|
||||||
|
{
|
||||||
|
if ($uri instanceof UriInterface) {
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uri($uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7\Factory;
|
||||||
|
|
||||||
|
use Nyholm\Psr7\{Request, Response, ServerRequest, Stream, UploadedFile, Uri};
|
||||||
|
use Psr\Http\Message\{RequestFactoryInterface, RequestInterface, ResponseFactoryInterface, ResponseInterface, ServerRequestFactoryInterface, ServerRequestInterface, StreamFactoryInterface, StreamInterface, UploadedFileFactoryInterface, UploadedFileInterface, UriFactoryInterface, UriInterface};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*/
|
||||||
|
class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
|
||||||
|
{
|
||||||
|
public function createRequest(string $method, $uri): RequestInterface
|
||||||
|
{
|
||||||
|
return new Request($method, $uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
|
||||||
|
{
|
||||||
|
if (2 > \func_num_args()) {
|
||||||
|
// This will make the Response class to use a custom reasonPhrase
|
||||||
|
$reasonPhrase = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response($code, [], null, '1.1', $reasonPhrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createStream(string $content = ''): StreamInterface
|
||||||
|
{
|
||||||
|
return Stream::create($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
|
||||||
|
{
|
||||||
|
if ('' === $filename) {
|
||||||
|
throw new \RuntimeException('Path cannot be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $resource = @\fopen($filename, $mode)) {
|
||||||
|
if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
|
||||||
|
throw new \InvalidArgumentException(\sprintf('The mode "%s" is invalid.', $mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $filename, \error_get_last()['message'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream::create($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createStreamFromResource($resource): StreamInterface
|
||||||
|
{
|
||||||
|
return Stream::create($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface
|
||||||
|
{
|
||||||
|
if (null === $size) {
|
||||||
|
$size = $stream->getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createUri(string $uri = ''): UriInterface
|
||||||
|
{
|
||||||
|
return new Uri($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
|
||||||
|
{
|
||||||
|
return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
+235
@@ -0,0 +1,235 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\MessageInterface;
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait implementing functionality common to requests and responses.
|
||||||
|
*
|
||||||
|
* @author Michael Dowling and contributors to guzzlehttp/psr7
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
|
||||||
|
*/
|
||||||
|
trait MessageTrait
|
||||||
|
{
|
||||||
|
/** @var array Map of all registered headers, as original name => array of values */
|
||||||
|
private $headers = [];
|
||||||
|
|
||||||
|
/** @var array Map of lowercase header name => original name at registration */
|
||||||
|
private $headerNames = [];
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $protocol = '1.1';
|
||||||
|
|
||||||
|
/** @var StreamInterface|null */
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
public function getProtocolVersion(): string
|
||||||
|
{
|
||||||
|
return $this->protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withProtocolVersion($version): MessageInterface
|
||||||
|
{
|
||||||
|
if (!\is_scalar($version)) {
|
||||||
|
throw new \InvalidArgumentException('Protocol version must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->protocol === $version) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->protocol = (string) $version;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeaders(): array
|
||||||
|
{
|
||||||
|
return $this->headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasHeader($header): bool
|
||||||
|
{
|
||||||
|
return isset($this->headerNames[\strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeader($header): array
|
||||||
|
{
|
||||||
|
if (!\is_string($header)) {
|
||||||
|
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
|
||||||
|
if (!isset($this->headerNames[$header])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = $this->headerNames[$header];
|
||||||
|
|
||||||
|
return $this->headers[$header];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeaderLine($header): string
|
||||||
|
{
|
||||||
|
return \implode(', ', $this->getHeader($header));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withHeader($header, $value): MessageInterface
|
||||||
|
{
|
||||||
|
$value = $this->validateAndTrimHeader($header, $value);
|
||||||
|
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
if (isset($new->headerNames[$normalized])) {
|
||||||
|
unset($new->headers[$new->headerNames[$normalized]]);
|
||||||
|
}
|
||||||
|
$new->headerNames[$normalized] = $header;
|
||||||
|
$new->headers[$header] = $value;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withAddedHeader($header, $value): MessageInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($header) || '' === $header) {
|
||||||
|
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->setHeaders([$header => $value]);
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withoutHeader($header): MessageInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($header)) {
|
||||||
|
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
|
||||||
|
if (!isset($this->headerNames[$normalized])) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = $this->headerNames[$normalized];
|
||||||
|
$new = clone $this;
|
||||||
|
unset($new->headers[$header], $new->headerNames[$normalized]);
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBody(): StreamInterface
|
||||||
|
{
|
||||||
|
if (null === $this->stream) {
|
||||||
|
$this->stream = Stream::create('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withBody(StreamInterface $body): MessageInterface
|
||||||
|
{
|
||||||
|
if ($body === $this->stream) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->stream = $body;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setHeaders(array $headers): void
|
||||||
|
{
|
||||||
|
foreach ($headers as $header => $value) {
|
||||||
|
if (\is_int($header)) {
|
||||||
|
// If a header name was set to a numeric string, PHP will cast the key to an int.
|
||||||
|
// We must cast it back to a string in order to comply with validation.
|
||||||
|
$header = (string) $header;
|
||||||
|
}
|
||||||
|
$value = $this->validateAndTrimHeader($header, $value);
|
||||||
|
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
|
||||||
|
if (isset($this->headerNames[$normalized])) {
|
||||||
|
$header = $this->headerNames[$normalized];
|
||||||
|
$this->headers[$header] = \array_merge($this->headers[$header], $value);
|
||||||
|
} else {
|
||||||
|
$this->headerNames[$normalized] = $header;
|
||||||
|
$this->headers[$header] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure the header complies with RFC 7230.
|
||||||
|
*
|
||||||
|
* Header names must be a non-empty string consisting of token characters.
|
||||||
|
*
|
||||||
|
* Header values must be strings consisting of visible characters with all optional
|
||||||
|
* leading and trailing whitespace stripped. This method will always strip such
|
||||||
|
* optional whitespace. Note that the method does not allow folding whitespace within
|
||||||
|
* the values as this was deprecated for almost all instances by the RFC.
|
||||||
|
*
|
||||||
|
* header-field = field-name ":" OWS field-value OWS
|
||||||
|
* field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
|
||||||
|
* / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
|
||||||
|
* OWS = *( SP / HTAB )
|
||||||
|
* field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
|
||||||
|
*
|
||||||
|
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||||
|
*/
|
||||||
|
private function validateAndTrimHeader($header, $values): array
|
||||||
|
{
|
||||||
|
if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@D", $header)) {
|
||||||
|
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_array($values)) {
|
||||||
|
// This is simple, just one value.
|
||||||
|
if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) {
|
||||||
|
throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [\trim((string) $values, " \t")];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($values)) {
|
||||||
|
throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert Non empty array
|
||||||
|
$returnValues = [];
|
||||||
|
foreach ($values as $v) {
|
||||||
|
if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@D", (string) $v)) {
|
||||||
|
throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings');
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnValues[] = \trim((string) $v, " \t");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+47
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\{RequestInterface, StreamInterface, UriInterface};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*/
|
||||||
|
class Request implements RequestInterface
|
||||||
|
{
|
||||||
|
use MessageTrait;
|
||||||
|
use RequestTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $method HTTP method
|
||||||
|
* @param string|UriInterface $uri URI
|
||||||
|
* @param array $headers Request headers
|
||||||
|
* @param string|resource|StreamInterface|null $body Request body
|
||||||
|
* @param string $version Protocol version
|
||||||
|
*/
|
||||||
|
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1')
|
||||||
|
{
|
||||||
|
if (!($uri instanceof UriInterface)) {
|
||||||
|
$uri = new Uri($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->method = $method;
|
||||||
|
$this->uri = $uri;
|
||||||
|
$this->setHeaders($headers);
|
||||||
|
$this->protocol = $version;
|
||||||
|
|
||||||
|
if (!$this->hasHeader('Host')) {
|
||||||
|
$this->updateHostFromUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got no body, defer initialization of the stream until Request::getBody()
|
||||||
|
if ('' !== $body && null !== $body) {
|
||||||
|
$this->stream = Stream::create($body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Michael Dowling and contributors to guzzlehttp/psr7
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
|
||||||
|
*/
|
||||||
|
trait RequestTrait
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $method;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $requestTarget;
|
||||||
|
|
||||||
|
/** @var UriInterface|null */
|
||||||
|
private $uri;
|
||||||
|
|
||||||
|
public function getRequestTarget(): string
|
||||||
|
{
|
||||||
|
if (null !== $this->requestTarget) {
|
||||||
|
return $this->requestTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $target = $this->uri->getPath()) {
|
||||||
|
$target = '/';
|
||||||
|
}
|
||||||
|
if ('' !== $this->uri->getQuery()) {
|
||||||
|
$target .= '?' . $this->uri->getQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withRequestTarget($requestTarget): RequestInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($requestTarget)) {
|
||||||
|
throw new \InvalidArgumentException('Request target must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\preg_match('#\s#', $requestTarget)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->requestTarget = $requestTarget;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethod(): string
|
||||||
|
{
|
||||||
|
return $this->method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withMethod($method): RequestInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($method)) {
|
||||||
|
throw new \InvalidArgumentException('Method must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->method = $method;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUri(): UriInterface
|
||||||
|
{
|
||||||
|
return $this->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
|
||||||
|
{
|
||||||
|
if ($uri === $this->uri) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->uri = $uri;
|
||||||
|
|
||||||
|
if (!$preserveHost || !$this->hasHeader('Host')) {
|
||||||
|
$new->updateHostFromUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateHostFromUri(): void
|
||||||
|
{
|
||||||
|
if ('' === $host = $this->uri->getHost()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== ($port = $this->uri->getPort())) {
|
||||||
|
$host .= ':' . $port;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->headerNames['host'])) {
|
||||||
|
$header = $this->headerNames['host'];
|
||||||
|
} else {
|
||||||
|
$this->headerNames['host'] = $header = 'Host';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Host is the first header.
|
||||||
|
// See: http://tools.ietf.org/html/rfc7230#section-5.4
|
||||||
|
$this->headers = [$header => [$host]] + $this->headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+93
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\{ResponseInterface, StreamInterface};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Michael Dowling and contributors to guzzlehttp/psr7
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*/
|
||||||
|
class Response implements ResponseInterface
|
||||||
|
{
|
||||||
|
use MessageTrait;
|
||||||
|
|
||||||
|
/** @var array Map of standard HTTP status code/reason phrases */
|
||||||
|
private const PHRASES = [
|
||||||
|
100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing',
|
||||||
|
200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported',
|
||||||
|
300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect',
|
||||||
|
400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons',
|
||||||
|
500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $reasonPhrase = '';
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $statusCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $status Status code
|
||||||
|
* @param array $headers Response headers
|
||||||
|
* @param string|resource|StreamInterface|null $body Response body
|
||||||
|
* @param string $version Protocol version
|
||||||
|
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
|
||||||
|
*/
|
||||||
|
public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null)
|
||||||
|
{
|
||||||
|
// If we got no body, defer initialization of the stream until Response::getBody()
|
||||||
|
if ('' !== $body && null !== $body) {
|
||||||
|
$this->stream = Stream::create($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->statusCode = $status;
|
||||||
|
$this->setHeaders($headers);
|
||||||
|
if (null === $reason && isset(self::PHRASES[$this->statusCode])) {
|
||||||
|
$this->reasonPhrase = self::PHRASES[$status];
|
||||||
|
} else {
|
||||||
|
$this->reasonPhrase = $reason ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->protocol = $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatusCode(): int
|
||||||
|
{
|
||||||
|
return $this->statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReasonPhrase(): string
|
||||||
|
{
|
||||||
|
return $this->reasonPhrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withStatus($code, $reasonPhrase = ''): ResponseInterface
|
||||||
|
{
|
||||||
|
if (!\is_int($code) && !\is_string($code)) {
|
||||||
|
throw new \InvalidArgumentException('Status code has to be an integer');
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = (int) $code;
|
||||||
|
if ($code < 100 || $code > 599) {
|
||||||
|
throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code));
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->statusCode = $code;
|
||||||
|
if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) {
|
||||||
|
$reasonPhrase = self::PHRASES[$new->statusCode];
|
||||||
|
}
|
||||||
|
$new->reasonPhrase = $reasonPhrase;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\{ServerRequestInterface, StreamInterface, UploadedFileInterface, UriInterface};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Michael Dowling and contributors to guzzlehttp/psr7
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*/
|
||||||
|
class ServerRequest implements ServerRequestInterface
|
||||||
|
{
|
||||||
|
use MessageTrait;
|
||||||
|
use RequestTrait;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $attributes = [];
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $cookieParams = [];
|
||||||
|
|
||||||
|
/** @var array|object|null */
|
||||||
|
private $parsedBody;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $queryParams = [];
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $serverParams;
|
||||||
|
|
||||||
|
/** @var UploadedFileInterface[] */
|
||||||
|
private $uploadedFiles = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $method HTTP method
|
||||||
|
* @param string|UriInterface $uri URI
|
||||||
|
* @param array $headers Request headers
|
||||||
|
* @param string|resource|StreamInterface|null $body Request body
|
||||||
|
* @param string $version Protocol version
|
||||||
|
* @param array $serverParams Typically the $_SERVER superglobal
|
||||||
|
*/
|
||||||
|
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = [])
|
||||||
|
{
|
||||||
|
$this->serverParams = $serverParams;
|
||||||
|
|
||||||
|
if (!($uri instanceof UriInterface)) {
|
||||||
|
$uri = new Uri($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->method = $method;
|
||||||
|
$this->uri = $uri;
|
||||||
|
$this->setHeaders($headers);
|
||||||
|
$this->protocol = $version;
|
||||||
|
\parse_str($uri->getQuery(), $this->queryParams);
|
||||||
|
|
||||||
|
if (!$this->hasHeader('Host')) {
|
||||||
|
$this->updateHostFromUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got no body, defer initialization of the stream until ServerRequest::getBody()
|
||||||
|
if ('' !== $body && null !== $body) {
|
||||||
|
$this->stream = Stream::create($body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getServerParams(): array
|
||||||
|
{
|
||||||
|
return $this->serverParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUploadedFiles(): array
|
||||||
|
{
|
||||||
|
return $this->uploadedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
|
||||||
|
{
|
||||||
|
$new = clone $this;
|
||||||
|
$new->uploadedFiles = $uploadedFiles;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCookieParams(): array
|
||||||
|
{
|
||||||
|
return $this->cookieParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withCookieParams(array $cookies): ServerRequestInterface
|
||||||
|
{
|
||||||
|
$new = clone $this;
|
||||||
|
$new->cookieParams = $cookies;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryParams(): array
|
||||||
|
{
|
||||||
|
return $this->queryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withQueryParams(array $query): ServerRequestInterface
|
||||||
|
{
|
||||||
|
$new = clone $this;
|
||||||
|
$new->queryParams = $query;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|object|null
|
||||||
|
*/
|
||||||
|
public function getParsedBody()
|
||||||
|
{
|
||||||
|
return $this->parsedBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withParsedBody($data): ServerRequestInterface
|
||||||
|
{
|
||||||
|
if (!\is_array($data) && !\is_object($data) && null !== $data) {
|
||||||
|
throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null');
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->parsedBody = $data;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttributes(): array
|
||||||
|
{
|
||||||
|
return $this->attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getAttribute($attribute, $default = null)
|
||||||
|
{
|
||||||
|
if (!\is_string($attribute)) {
|
||||||
|
throw new \InvalidArgumentException('Attribute name must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === \array_key_exists($attribute, $this->attributes)) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->attributes[$attribute];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withAttribute($attribute, $value): ServerRequestInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($attribute)) {
|
||||||
|
throw new \InvalidArgumentException('Attribute name must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->attributes[$attribute] = $value;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withoutAttribute($attribute): ServerRequestInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($attribute)) {
|
||||||
|
throw new \InvalidArgumentException('Attribute name must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === \array_key_exists($attribute, $this->attributes)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
unset($new->attributes[$attribute]);
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+399
@@ -0,0 +1,399 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Michael Dowling and contributors to guzzlehttp/psr7
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*/
|
||||||
|
class Stream implements StreamInterface
|
||||||
|
{
|
||||||
|
use StreamTrait;
|
||||||
|
|
||||||
|
/** @var resource|null A resource reference */
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $seekable;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $readable;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $writable;
|
||||||
|
|
||||||
|
/** @var array|mixed|void|bool|null */
|
||||||
|
private $uri;
|
||||||
|
|
||||||
|
/** @var int|null */
|
||||||
|
private $size;
|
||||||
|
|
||||||
|
/** @var array Hash of readable and writable stream types */
|
||||||
|
private const READ_WRITE_HASH = [
|
||||||
|
'read' => [
|
||||||
|
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
|
||||||
|
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
|
||||||
|
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
|
||||||
|
'x+t' => true, 'c+t' => true, 'a+' => true,
|
||||||
|
],
|
||||||
|
'write' => [
|
||||||
|
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
|
||||||
|
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
|
||||||
|
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
|
||||||
|
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $body
|
||||||
|
*/
|
||||||
|
public function __construct($body)
|
||||||
|
{
|
||||||
|
if (!\is_resource($body)) {
|
||||||
|
throw new \InvalidArgumentException('First argument to Stream::__construct() must be resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stream = $body;
|
||||||
|
$meta = \stream_get_meta_data($this->stream);
|
||||||
|
$this->seekable = $meta['seekable'] && 0 === \fseek($this->stream, 0, \SEEK_CUR);
|
||||||
|
$this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]);
|
||||||
|
$this->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new PSR-7 stream.
|
||||||
|
*
|
||||||
|
* @param string|resource|StreamInterface $body
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public static function create($body = ''): StreamInterface
|
||||||
|
{
|
||||||
|
if ($body instanceof StreamInterface) {
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_string($body)) {
|
||||||
|
if (200000 <= \strlen($body)) {
|
||||||
|
$body = self::openZvalStream($body);
|
||||||
|
} else {
|
||||||
|
$resource = \fopen('php://memory', 'r+');
|
||||||
|
\fwrite($resource, $body);
|
||||||
|
\fseek($resource, 0);
|
||||||
|
$body = $resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_resource($body)) {
|
||||||
|
throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream when the destructed.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
if (isset($this->stream)) {
|
||||||
|
if (\is_resource($this->stream)) {
|
||||||
|
\fclose($this->stream);
|
||||||
|
}
|
||||||
|
$this->detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detach()
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->stream;
|
||||||
|
unset($this->stream);
|
||||||
|
$this->size = $this->uri = null;
|
||||||
|
$this->readable = $this->writable = $this->seekable = false;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUri()
|
||||||
|
{
|
||||||
|
if (false !== $this->uri) {
|
||||||
|
$this->uri = $this->getMetadata('uri') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSize(): ?int
|
||||||
|
{
|
||||||
|
if (null !== $this->size) {
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the stat cache if the stream has a URI
|
||||||
|
if ($uri = $this->getUri()) {
|
||||||
|
\clearstatcache(true, $uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats = \fstat($this->stream);
|
||||||
|
if (isset($stats['size'])) {
|
||||||
|
$this->size = $stats['size'];
|
||||||
|
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tell(): int
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $result = @\ftell($this->stream)) {
|
||||||
|
throw new \RuntimeException('Unable to determine stream position: ' . (\error_get_last()['message'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eof(): bool
|
||||||
|
{
|
||||||
|
return !isset($this->stream) || \feof($this->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSeekable(): bool
|
||||||
|
{
|
||||||
|
return $this->seekable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function seek($offset, $whence = \SEEK_SET): void
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->seekable) {
|
||||||
|
throw new \RuntimeException('Stream is not seekable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-1 === \fseek($this->stream, $offset, $whence)) {
|
||||||
|
throw new \RuntimeException('Unable to seek to stream position "' . $offset . '" with whence ' . \var_export($whence, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return $this->writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function write($string): int
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->writable) {
|
||||||
|
throw new \RuntimeException('Cannot write to a non-writable stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't know the size after writing anything
|
||||||
|
$this->size = null;
|
||||||
|
|
||||||
|
if (false === $result = @\fwrite($this->stream, $string)) {
|
||||||
|
throw new \RuntimeException('Unable to write to stream: ' . (\error_get_last()['message'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return $this->readable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read($length): string
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->readable) {
|
||||||
|
throw new \RuntimeException('Cannot read from non-readable stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $result = @\fread($this->stream, $length)) {
|
||||||
|
throw new \RuntimeException('Unable to read from stream: ' . (\error_get_last()['message'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exception = null;
|
||||||
|
|
||||||
|
\set_error_handler(static function ($type, $message) use (&$exception) {
|
||||||
|
throw $exception = new \RuntimeException('Unable to read stream contents: ' . $message);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return \stream_get_contents($this->stream);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
throw $e === $exception ? $e : new \RuntimeException('Unable to read stream contents: ' . $e->getMessage(), 0, $e);
|
||||||
|
} finally {
|
||||||
|
\restore_error_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getMetadata($key = null)
|
||||||
|
{
|
||||||
|
if (null !== $key && !\is_string($key)) {
|
||||||
|
throw new \InvalidArgumentException('Metadata key must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return $key ? null : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = \stream_get_meta_data($this->stream);
|
||||||
|
|
||||||
|
if (null === $key) {
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $meta[$key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function openZvalStream(string $body)
|
||||||
|
{
|
||||||
|
static $wrapper;
|
||||||
|
|
||||||
|
$wrapper ?? \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper = \get_class(new class() {
|
||||||
|
public $context;
|
||||||
|
|
||||||
|
private $data;
|
||||||
|
private $position = 0;
|
||||||
|
|
||||||
|
public function stream_open(): bool
|
||||||
|
{
|
||||||
|
$this->data = \stream_context_get_options($this->context)['Nyholm-Psr7-Zval']['data'];
|
||||||
|
\stream_context_set_option($this->context, 'Nyholm-Psr7-Zval', 'data', null);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read(int $count): string
|
||||||
|
{
|
||||||
|
$result = \substr($this->data, $this->position, $count);
|
||||||
|
$this->position += \strlen($result);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_write(string $data): int
|
||||||
|
{
|
||||||
|
$this->data = \substr_replace($this->data, $data, $this->position, \strlen($data));
|
||||||
|
$this->position += \strlen($data);
|
||||||
|
|
||||||
|
return \strlen($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_tell(): int
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof(): bool
|
||||||
|
{
|
||||||
|
return \strlen($this->data) <= $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'mode' => 33206, // POSIX_S_IFREG | 0666
|
||||||
|
'nlink' => 1,
|
||||||
|
'rdev' => -1,
|
||||||
|
'size' => \strlen($this->data),
|
||||||
|
'blksize' => -1,
|
||||||
|
'blocks' => -1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_seek(int $offset, int $whence): bool
|
||||||
|
{
|
||||||
|
if (\SEEK_SET === $whence && (0 <= $offset && \strlen($this->data) >= $offset)) {
|
||||||
|
$this->position = $offset;
|
||||||
|
} elseif (\SEEK_CUR === $whence && 0 <= $offset) {
|
||||||
|
$this->position += $offset;
|
||||||
|
} elseif (\SEEK_END === $whence && (0 > $offset && 0 <= $offset = \strlen($this->data) + $offset)) {
|
||||||
|
$this->position = $offset;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_set_option(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_truncate(int $new_size): bool
|
||||||
|
{
|
||||||
|
if ($new_size) {
|
||||||
|
$this->data = \substr($this->data, 0, $new_size);
|
||||||
|
$this->position = \min($this->position, $new_size);
|
||||||
|
} else {
|
||||||
|
$this->data = '';
|
||||||
|
$this->position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
$context = \stream_context_create(['Nyholm-Psr7-Zval' => ['data' => $body]]);
|
||||||
|
|
||||||
|
if (!$stream = @\fopen('Nyholm-Psr7-Zval://', 'r+', false, $context)) {
|
||||||
|
\stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper);
|
||||||
|
$stream = \fopen('Nyholm-Psr7-Zval://', 'r+', false, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
use Symfony\Component\Debug\ErrorHandler as SymfonyLegacyErrorHandler;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler;
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 70400 || (new \ReflectionMethod(StreamInterface::class, '__toString'))->hasReturnType()) {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
trait StreamTrait
|
||||||
|
{
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
if ($this->isSeekable()) {
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getContents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
trait StreamTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->isSeekable()) {
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getContents();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
if (\is_array($errorHandler = \set_error_handler('var_dump'))) {
|
||||||
|
$errorHandler = $errorHandler[0] ?? null;
|
||||||
|
}
|
||||||
|
\restore_error_handler();
|
||||||
|
|
||||||
|
if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) {
|
||||||
|
return \trigger_error((string) $e, \E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\{StreamInterface, UploadedFileInterface};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Michael Dowling and contributors to guzzlehttp/psr7
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*/
|
||||||
|
class UploadedFile implements UploadedFileInterface
|
||||||
|
{
|
||||||
|
/** @var array */
|
||||||
|
private const ERRORS = [
|
||||||
|
\UPLOAD_ERR_OK => 1,
|
||||||
|
\UPLOAD_ERR_INI_SIZE => 1,
|
||||||
|
\UPLOAD_ERR_FORM_SIZE => 1,
|
||||||
|
\UPLOAD_ERR_PARTIAL => 1,
|
||||||
|
\UPLOAD_ERR_NO_FILE => 1,
|
||||||
|
\UPLOAD_ERR_NO_TMP_DIR => 1,
|
||||||
|
\UPLOAD_ERR_CANT_WRITE => 1,
|
||||||
|
\UPLOAD_ERR_EXTENSION => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $clientFilename;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $clientMediaType;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $file;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $moved = false;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $size;
|
||||||
|
|
||||||
|
/** @var StreamInterface|null */
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StreamInterface|string|resource $streamOrFile
|
||||||
|
* @param int $size
|
||||||
|
* @param int $errorStatus
|
||||||
|
* @param string|null $clientFilename
|
||||||
|
* @param string|null $clientMediaType
|
||||||
|
*/
|
||||||
|
public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
|
||||||
|
{
|
||||||
|
if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
|
||||||
|
throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === \is_int($size)) {
|
||||||
|
throw new \InvalidArgumentException('Upload file size must be an integer');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $clientFilename && !\is_string($clientFilename)) {
|
||||||
|
throw new \InvalidArgumentException('Upload file client filename must be a string or null');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $clientMediaType && !\is_string($clientMediaType)) {
|
||||||
|
throw new \InvalidArgumentException('Upload file client media type must be a string or null');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->error = $errorStatus;
|
||||||
|
$this->size = $size;
|
||||||
|
$this->clientFilename = $clientFilename;
|
||||||
|
$this->clientMediaType = $clientMediaType;
|
||||||
|
|
||||||
|
if (\UPLOAD_ERR_OK === $this->error) {
|
||||||
|
// Depending on the value set file or stream variable.
|
||||||
|
if (\is_string($streamOrFile) && '' !== $streamOrFile) {
|
||||||
|
$this->file = $streamOrFile;
|
||||||
|
} elseif (\is_resource($streamOrFile)) {
|
||||||
|
$this->stream = Stream::create($streamOrFile);
|
||||||
|
} elseif ($streamOrFile instanceof StreamInterface) {
|
||||||
|
$this->stream = $streamOrFile;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \RuntimeException if is moved or not ok
|
||||||
|
*/
|
||||||
|
private function validateActive(): void
|
||||||
|
{
|
||||||
|
if (\UPLOAD_ERR_OK !== $this->error) {
|
||||||
|
throw new \RuntimeException('Cannot retrieve stream due to upload error');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->moved) {
|
||||||
|
throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStream(): StreamInterface
|
||||||
|
{
|
||||||
|
$this->validateActive();
|
||||||
|
|
||||||
|
if ($this->stream instanceof StreamInterface) {
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $resource = @\fopen($this->file, 'r')) {
|
||||||
|
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $this->file, \error_get_last()['message'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream::create($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveTo($targetPath): void
|
||||||
|
{
|
||||||
|
$this->validateActive();
|
||||||
|
|
||||||
|
if (!\is_string($targetPath) || '' === $targetPath) {
|
||||||
|
throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->file) {
|
||||||
|
$this->moved = 'cli' === \PHP_SAPI ? @\rename($this->file, $targetPath) : @\move_uploaded_file($this->file, $targetPath);
|
||||||
|
|
||||||
|
if (false === $this->moved) {
|
||||||
|
throw new \RuntimeException(\sprintf('Uploaded file could not be moved to "%s": %s', $targetPath, \error_get_last()['message'] ?? ''));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$stream = $this->getStream();
|
||||||
|
if ($stream->isSeekable()) {
|
||||||
|
$stream->rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $resource = @\fopen($targetPath, 'w')) {
|
||||||
|
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $targetPath, \error_get_last()['message'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
$dest = Stream::create($resource);
|
||||||
|
|
||||||
|
while (!$stream->eof()) {
|
||||||
|
if (!$dest->write($stream->read(1048576))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSize(): int
|
||||||
|
{
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getError(): int
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClientFilename(): ?string
|
||||||
|
{
|
||||||
|
return $this->clientFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClientMediaType(): ?string
|
||||||
|
{
|
||||||
|
return $this->clientMediaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+356
@@ -0,0 +1,356 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Nyholm\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PSR-7 URI implementation.
|
||||||
|
*
|
||||||
|
* @author Michael Dowling
|
||||||
|
* @author Tobias Schultze
|
||||||
|
* @author Matthew Weier O'Phinney
|
||||||
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
|
* @author Martijn van der Ven <martijn@vanderven.se>
|
||||||
|
*
|
||||||
|
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
|
||||||
|
*/
|
||||||
|
class Uri implements UriInterface
|
||||||
|
{
|
||||||
|
private const SCHEMES = ['http' => 80, 'https' => 443];
|
||||||
|
|
||||||
|
private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
|
||||||
|
|
||||||
|
private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
|
||||||
|
|
||||||
|
private const CHAR_GEN_DELIMS = ':\/\?#\[\]@';
|
||||||
|
|
||||||
|
/** @var string Uri scheme. */
|
||||||
|
private $scheme = '';
|
||||||
|
|
||||||
|
/** @var string Uri user info. */
|
||||||
|
private $userInfo = '';
|
||||||
|
|
||||||
|
/** @var string Uri host. */
|
||||||
|
private $host = '';
|
||||||
|
|
||||||
|
/** @var int|null Uri port. */
|
||||||
|
private $port;
|
||||||
|
|
||||||
|
/** @var string Uri path. */
|
||||||
|
private $path = '';
|
||||||
|
|
||||||
|
/** @var string Uri query string. */
|
||||||
|
private $query = '';
|
||||||
|
|
||||||
|
/** @var string Uri fragment. */
|
||||||
|
private $fragment = '';
|
||||||
|
|
||||||
|
public function __construct(string $uri = '')
|
||||||
|
{
|
||||||
|
if ('' !== $uri) {
|
||||||
|
if (false === $parts = \parse_url($uri)) {
|
||||||
|
throw new \InvalidArgumentException(\sprintf('Unable to parse URI: "%s"', $uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply parse_url parts to a URI.
|
||||||
|
$this->scheme = isset($parts['scheme']) ? \strtr($parts['scheme'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : '';
|
||||||
|
$this->userInfo = $parts['user'] ?? '';
|
||||||
|
$this->host = isset($parts['host']) ? \strtr($parts['host'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : '';
|
||||||
|
$this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
|
||||||
|
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
|
||||||
|
$this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
|
||||||
|
$this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
|
||||||
|
if (isset($parts['pass'])) {
|
||||||
|
$this->userInfo .= ':' . $parts['pass'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScheme(): string
|
||||||
|
{
|
||||||
|
return $this->scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthority(): string
|
||||||
|
{
|
||||||
|
if ('' === $this->host) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$authority = $this->host;
|
||||||
|
if ('' !== $this->userInfo) {
|
||||||
|
$authority = $this->userInfo . '@' . $authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->port) {
|
||||||
|
$authority .= ':' . $this->port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserInfo(): string
|
||||||
|
{
|
||||||
|
return $this->userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHost(): string
|
||||||
|
{
|
||||||
|
return $this->host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPort(): ?int
|
||||||
|
{
|
||||||
|
return $this->port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath(): string
|
||||||
|
{
|
||||||
|
$path = $this->path;
|
||||||
|
|
||||||
|
if ('' !== $path && '/' !== $path[0]) {
|
||||||
|
if ('' !== $this->host) {
|
||||||
|
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
|
||||||
|
$path = '/' . $path;
|
||||||
|
}
|
||||||
|
} elseif (isset($path[1]) && '/' === $path[1]) {
|
||||||
|
// If the path is starting with more than one "/", the
|
||||||
|
// starting slashes MUST be reduced to one.
|
||||||
|
$path = '/' . \ltrim($path, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQuery(): string
|
||||||
|
{
|
||||||
|
return $this->query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFragment(): string
|
||||||
|
{
|
||||||
|
return $this->fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withScheme($scheme): UriInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($scheme)) {
|
||||||
|
throw new \InvalidArgumentException('Scheme must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->scheme === $scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->scheme = $scheme;
|
||||||
|
$new->port = $new->filterPort($new->port);
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withUserInfo($user, $password = null): UriInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($user)) {
|
||||||
|
throw new \InvalidArgumentException('User must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $user);
|
||||||
|
if (null !== $password && '' !== $password) {
|
||||||
|
if (!\is_string($password)) {
|
||||||
|
throw new \InvalidArgumentException('Password must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$info .= ':' . \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $password);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->userInfo === $info) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->userInfo = $info;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withHost($host): UriInterface
|
||||||
|
{
|
||||||
|
if (!\is_string($host)) {
|
||||||
|
throw new \InvalidArgumentException('Host must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->host === $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->host = $host;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withPort($port): UriInterface
|
||||||
|
{
|
||||||
|
if ($this->port === $port = $this->filterPort($port)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->port = $port;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withPath($path): UriInterface
|
||||||
|
{
|
||||||
|
if ($this->path === $path = $this->filterPath($path)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->path = $path;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withQuery($query): UriInterface
|
||||||
|
{
|
||||||
|
if ($this->query === $query = $this->filterQueryAndFragment($query)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->query = $query;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withFragment($fragment): UriInterface
|
||||||
|
{
|
||||||
|
if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = clone $this;
|
||||||
|
$new->fragment = $fragment;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a URI string from its various parts.
|
||||||
|
*/
|
||||||
|
private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string
|
||||||
|
{
|
||||||
|
$uri = '';
|
||||||
|
if ('' !== $scheme) {
|
||||||
|
$uri .= $scheme . ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $authority) {
|
||||||
|
$uri .= '//' . $authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $path) {
|
||||||
|
if ('/' !== $path[0]) {
|
||||||
|
if ('' !== $authority) {
|
||||||
|
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
|
||||||
|
$path = '/' . $path;
|
||||||
|
}
|
||||||
|
} elseif (isset($path[1]) && '/' === $path[1]) {
|
||||||
|
if ('' === $authority) {
|
||||||
|
// If the path is starting with more than one "/" and no authority is present, the
|
||||||
|
// starting slashes MUST be reduced to one.
|
||||||
|
$path = '/' . \ltrim($path, '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$uri .= $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $query) {
|
||||||
|
$uri .= '?' . $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $fragment) {
|
||||||
|
$uri .= '#' . $fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is a given port non-standard for the current scheme?
|
||||||
|
*/
|
||||||
|
private static function isNonStandardPort(string $scheme, int $port): bool
|
||||||
|
{
|
||||||
|
return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filterPort($port): ?int
|
||||||
|
{
|
||||||
|
if (null === $port) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$port = (int) $port;
|
||||||
|
if (0 > $port || 0xFFFF < $port) {
|
||||||
|
throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filterPath($path): string
|
||||||
|
{
|
||||||
|
if (!\is_string($path)) {
|
||||||
|
throw new \InvalidArgumentException('Path must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filterQueryAndFragment($str): string
|
||||||
|
{
|
||||||
|
if (!\is_string($str)) {
|
||||||
|
throw new \InvalidArgumentException('Query and fragment must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function rawurlencodeMatchZero(array $match): string
|
||||||
|
{
|
||||||
|
return \rawurlencode($match[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/comparison",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Interfaces and helper trait for comparing objects. Comparator for sort and filter applications.",
|
||||||
|
"homepage": "https://phrity.sirn.se/comparison",
|
||||||
|
"keywords": ["comparison", "equalable", "comparable", "comparator", "sort", "filter"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Comparison\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Mock\\": "tests/Mock/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for Comparable inteface.
|
||||||
|
* @package Phrity > Comparison
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Comparison;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for comparable instances.
|
||||||
|
*/
|
||||||
|
interface Comparable extends Equalable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Compare $this and $that and return result as comparison identifier as integer.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return integer Must return 0 if $this is equal to $that
|
||||||
|
* Must return -1 if $this is less than $that
|
||||||
|
* Must return 1 if $this is greater than $that
|
||||||
|
* @throws IncomparableException Must throw if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function compare(mixed $that): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is greater than $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is greater than $that
|
||||||
|
* @throws IncomparableException Must throw if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function greaterThan(mixed $that): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is greater than or equal to $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is greater than or equal to $that
|
||||||
|
* @throws IncomparableException Must throw if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function greaterThanOrEqual(mixed $that): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is less than $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is less than $that
|
||||||
|
* @throws IncomparableException Must throw if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function lessThan(mixed $that): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is less than or equal to $this.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is less than or equal to $this
|
||||||
|
* @throws IncomparableException Must throw if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function lessThanOrEqual(mixed $that): bool;
|
||||||
|
}
|
||||||
+200
@@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for Comparator.
|
||||||
|
* @package Phrity > Comparison
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Comparison;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for comparing and filtering.
|
||||||
|
*/
|
||||||
|
class Comparator
|
||||||
|
{
|
||||||
|
/** @var array<Comparable> */
|
||||||
|
private $comparables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If comparables supplied in constructor, they will be used as defaults an operations
|
||||||
|
* @param array<Comparable> $comparables List of objects implementing Comparable
|
||||||
|
*/
|
||||||
|
public function __construct(array $comparables = [])
|
||||||
|
{
|
||||||
|
$this->comparables = $comparables;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts array of comparable items, low to high
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The sorted list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function sort(array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
$comparables = $comparables ?: $this->comparables;
|
||||||
|
usort($comparables, function ($item_1, $item_2) {
|
||||||
|
$this->verifyComparable($item_1);
|
||||||
|
return $item_1->compare($item_2);
|
||||||
|
});
|
||||||
|
return $comparables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts array of comparable items, high to low
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The sorted list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function rsort(array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
$comparables = $comparables ?: $this->comparables;
|
||||||
|
usort($comparables, function ($item_1, $item_2) {
|
||||||
|
$this->verifyComparable($item_2);
|
||||||
|
return $item_2->compare($item_1);
|
||||||
|
});
|
||||||
|
return $comparables;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Filter methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter array of comparable items that equals condition
|
||||||
|
* @param Comparable $condition To compare against
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The filtered list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function equals(Comparable $condition, array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
return $this->applyFilter('equals', $condition, $comparables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter array of comparable items that are greater than condition
|
||||||
|
* @param Comparable $condition To compare against
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The filtered list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function greaterThan(Comparable $condition, array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
return $this->applyFilter('greaterThan', $condition, $comparables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter array of comparable items that are greater than or equals condition
|
||||||
|
* @param Comparable $condition To compare against
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The filtered list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function greaterThanOrEqual(Comparable $condition, array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
return $this->applyFilter('greaterThanOrEqual', $condition, $comparables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter array of comparable items that are less than condition
|
||||||
|
* @param Comparable $condition To compare against
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The filtered list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function lessThan(Comparable $condition, array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
return $this->applyFilter('lessThan', $condition, $comparables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter array of comparable items that are less than or equals condition
|
||||||
|
* @param Comparable $condition To compare against
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The filtered list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function lessThanOrEqual(Comparable $condition, array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
return $this->applyFilter('lessThanOrEqual', $condition, $comparables);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Select methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get minimum item from array of comparable items
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return Comparable|null The resolved instance
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function min(array|null $comparables = null): Comparable|null
|
||||||
|
{
|
||||||
|
return $this->applyReduction('lessThan', $comparables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get maximum item from array of comparable items
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return Comparable|null The resolved instance
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
public function max(array|null $comparables = null): Comparable|null
|
||||||
|
{
|
||||||
|
return $this->applyReduction('greaterThan', $comparables);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Private internal methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify input implements Comparable
|
||||||
|
* @param mixed $item Item to verify
|
||||||
|
* @throws IncomparableException Thrown if item do not implement Comparable
|
||||||
|
*/
|
||||||
|
private function verifyComparable(mixed $item): void
|
||||||
|
{
|
||||||
|
if (!$item instanceof Comparable) {
|
||||||
|
throw new IncomparableException('All items must implement Comparable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter array of comparable items according to condition instance and method
|
||||||
|
* @param string $method Comparison method to use
|
||||||
|
* @param Comparable $condition To compare against
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return array<Comparable> The filtered list
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
private function applyFilter(string $method, Comparable $condition, array|null $comparables = null): array
|
||||||
|
{
|
||||||
|
$comparables = $comparables ?: $this->comparables;
|
||||||
|
$filtered = array_filter($comparables, function ($item) use ($method, $condition) {
|
||||||
|
$this->verifyComparable($item);
|
||||||
|
return $item->$method($condition);
|
||||||
|
});
|
||||||
|
return array_values($filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce array of comparable items according comparison method
|
||||||
|
* @param string $method Comparison method to use
|
||||||
|
* @param array<Comparable>|null $comparables List of objects implementing Comparable
|
||||||
|
* @return Comparable|null The resolved instance
|
||||||
|
* @throws IncomparableException Thrown if any item in the list can not be compared
|
||||||
|
*/
|
||||||
|
private function applyReduction(string $method, array|null $comparables = null): Comparable|null
|
||||||
|
{
|
||||||
|
$comparables = $comparables ?: $this->comparables;
|
||||||
|
return array_reduce($comparables, function ($item_1, $item_2) use ($method) {
|
||||||
|
if (is_null($item_1)) {
|
||||||
|
return $item_2;
|
||||||
|
}
|
||||||
|
$this->verifyComparable($item_1);
|
||||||
|
return $item_1->$method($item_2) ? $item_1 : $item_2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for ComparisonTrait.
|
||||||
|
* @package Phrity > Comparison
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Comparison;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait that enables comparison methods.
|
||||||
|
*/
|
||||||
|
trait ComparisonTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* If $this is equal to $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is equal to $that
|
||||||
|
* @throws IncomparableException Thrown if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function equals(mixed $that): bool
|
||||||
|
{
|
||||||
|
return $this->compare($that) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is greater than $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is greater than $that
|
||||||
|
* @throws IncomparableException Thrown if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function greaterThan(mixed $that): bool
|
||||||
|
{
|
||||||
|
return $this->compare($that) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is greater than or equal to $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is greater than or equal to $that
|
||||||
|
* @throws IncomparableException Thrown if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function greaterThanOrEqual(mixed $that): bool
|
||||||
|
{
|
||||||
|
return $this->compare($that) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is less than $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is less than $that
|
||||||
|
* @throws IncomparableException Thrown if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function lessThan(mixed $that): bool
|
||||||
|
{
|
||||||
|
return $this->compare($that) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $this is less than or equal to $this.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is less than or equal to $this
|
||||||
|
* @throws IncomparableException Thrown if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function lessThanOrEqual(mixed $that): bool
|
||||||
|
{
|
||||||
|
return $this->compare($that) <= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for Equalable inteface.
|
||||||
|
* @package Phrity > Comparison
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Comparison;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for equalable instances.
|
||||||
|
*/
|
||||||
|
interface Equalable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* If $this is equal to $that.
|
||||||
|
* @param mixed $that The instance to compare with
|
||||||
|
* @return boolean True if $this is equal to $that
|
||||||
|
* @throws IncomparableException Must throw if $this can not be compared with $that
|
||||||
|
*/
|
||||||
|
public function equals(mixed $that): bool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for IncomparableException.
|
||||||
|
* @package Phrity > Comparison
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Comparison;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception that should be thrown when instances can not be compared.
|
||||||
|
*/
|
||||||
|
class IncomparableException extends InvalidArgumentException
|
||||||
|
{
|
||||||
|
}
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sören Jensen
|
||||||
|
|
||||||
|
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.
|
||||||
Vendored
+40
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/http",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Utilities and interfaces for handling HTTP.",
|
||||||
|
"homepage": "https://phrity.sirn.se/http",
|
||||||
|
"keywords": ["HTTP", "HTTP Factories", "HTTP Serializer", "PSR-7", "PSR-17"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Http\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Http\\Test\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/psr7": "^2.0",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"psr/http-factory-implementation": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Http;
|
||||||
|
|
||||||
|
use BadMethodCallException;
|
||||||
|
use Psr\Http\Message\{
|
||||||
|
RequestFactoryInterface,
|
||||||
|
ResponseFactoryInterface,
|
||||||
|
ServerRequestFactoryInterface,
|
||||||
|
StreamFactoryInterface,
|
||||||
|
UploadedFileFactoryInterface,
|
||||||
|
UriFactoryInterface,
|
||||||
|
RequestInterface,
|
||||||
|
ResponseInterface,
|
||||||
|
ServerRequestInterface,
|
||||||
|
StreamInterface,
|
||||||
|
UploadedFileInterface,
|
||||||
|
UriInterface,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phrity\Http\HttpFactory
|
||||||
|
*/
|
||||||
|
class HttpFactory implements
|
||||||
|
RequestFactoryInterface,
|
||||||
|
ResponseFactoryInterface,
|
||||||
|
ServerRequestFactoryInterface,
|
||||||
|
StreamFactoryInterface,
|
||||||
|
UploadedFileFactoryInterface,
|
||||||
|
UriFactoryInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private RequestFactoryInterface|null $requestFactory = null,
|
||||||
|
private ResponseFactoryInterface|null $responseFactory = null,
|
||||||
|
private ServerRequestFactoryInterface|null $serverRequestFactory = null,
|
||||||
|
private StreamFactoryInterface|null $streamFactory = null,
|
||||||
|
private UploadedFileFactoryInterface|null $uploadedFileFactory = null,
|
||||||
|
private UriFactoryInterface|null $uriFactory = null,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $method
|
||||||
|
* @param UriInterface|string $uri
|
||||||
|
*/
|
||||||
|
public function createRequest(string $method, mixed $uri): RequestInterface
|
||||||
|
{
|
||||||
|
if (is_null($this->requestFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createRequest not implemented.');
|
||||||
|
}
|
||||||
|
return $this->requestFactory->createRequest($method, $uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $code
|
||||||
|
* @param string $reasonPhrase
|
||||||
|
*/
|
||||||
|
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
|
||||||
|
{
|
||||||
|
if (is_null($this->responseFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createResponse not implemented.');
|
||||||
|
}
|
||||||
|
return $this->responseFactory->createResponse($code, $reasonPhrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $method
|
||||||
|
* @param UriInterface|string $uri
|
||||||
|
* @param array<string, mixed> $serverParams
|
||||||
|
*/
|
||||||
|
public function createServerRequest(string $method, mixed $uri, array $serverParams = []): ServerRequestInterface
|
||||||
|
{
|
||||||
|
if (is_null($this->serverRequestFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createServerRequest not implemented.');
|
||||||
|
}
|
||||||
|
return $this->serverRequestFactory->createServerRequest($method, $uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $content
|
||||||
|
*/
|
||||||
|
public function createStream(string $content = ''): StreamInterface
|
||||||
|
{
|
||||||
|
if (is_null($this->streamFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createStream not implemented.');
|
||||||
|
}
|
||||||
|
return $this->streamFactory->createStream($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filename
|
||||||
|
* @param string $mode
|
||||||
|
*/
|
||||||
|
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
|
||||||
|
{
|
||||||
|
if (is_null($this->streamFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createStreamFromFile not implemented.');
|
||||||
|
}
|
||||||
|
return $this->streamFactory->createStreamFromFile($filename, $mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $resource
|
||||||
|
*/
|
||||||
|
public function createStreamFromResource($resource): StreamInterface
|
||||||
|
{
|
||||||
|
if (is_null($this->streamFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createStreamFromResource not implemented.');
|
||||||
|
}
|
||||||
|
return $this->streamFactory->createStreamFromResource($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StreamInterface $stream
|
||||||
|
* @param int $size
|
||||||
|
* @param int $error
|
||||||
|
* @param string $clientFilename
|
||||||
|
* @param string $clientMediaType
|
||||||
|
*/
|
||||||
|
public function createUploadedFile(
|
||||||
|
StreamInterface $stream,
|
||||||
|
int|null $size = null,
|
||||||
|
int $error = UPLOAD_ERR_OK,
|
||||||
|
string|null $clientFilename = null,
|
||||||
|
string|null $clientMediaType = null
|
||||||
|
): UploadedFileInterface {
|
||||||
|
if (is_null($this->uploadedFileFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createUploadedFile not implemented.');
|
||||||
|
}
|
||||||
|
return $this->uploadedFileFactory->createUploadedFile(
|
||||||
|
$stream,
|
||||||
|
$size,
|
||||||
|
$error,
|
||||||
|
$clientFilename,
|
||||||
|
$clientMediaType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $uri The URI to parse.
|
||||||
|
*/
|
||||||
|
public function createUri(string $uri = ''): UriInterface
|
||||||
|
{
|
||||||
|
if (is_null($this->uriFactory)) {
|
||||||
|
throw new BadMethodCallException('HttpFactory.createUri not implemented.');
|
||||||
|
}
|
||||||
|
return $this->uriFactory->createUri($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(object ...$implementations): self
|
||||||
|
{
|
||||||
|
$created = new self();
|
||||||
|
foreach ($implementations as $implementation) {
|
||||||
|
if ($implementation instanceof RequestFactoryInterface) {
|
||||||
|
$created->requestFactory = $implementation;
|
||||||
|
}
|
||||||
|
if ($implementation instanceof ResponseFactoryInterface) {
|
||||||
|
$created->responseFactory = $implementation;
|
||||||
|
}
|
||||||
|
if ($implementation instanceof ServerRequestFactoryInterface) {
|
||||||
|
$created->serverRequestFactory = $implementation;
|
||||||
|
}
|
||||||
|
if ($implementation instanceof StreamFactoryInterface) {
|
||||||
|
$created->streamFactory = $implementation;
|
||||||
|
}
|
||||||
|
if ($implementation instanceof UploadedFileFactoryInterface) {
|
||||||
|
$created->uploadedFileFactory = $implementation;
|
||||||
|
}
|
||||||
|
if ($implementation instanceof UriFactoryInterface) {
|
||||||
|
$created->uriFactory = $implementation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $created;
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Http;
|
||||||
|
|
||||||
|
use DomainException;
|
||||||
|
use Psr\Http\Message\{
|
||||||
|
MessageInterface,
|
||||||
|
ResponseInterface,
|
||||||
|
RequestInterface,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phrity\Http\Serializer
|
||||||
|
*/
|
||||||
|
class Serializer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param RequestInterface $request
|
||||||
|
*/
|
||||||
|
public function request(RequestInterface $request): string
|
||||||
|
{
|
||||||
|
$status = $this->wrap(
|
||||||
|
'%s %s HTTP/%s',
|
||||||
|
$request->getMethod(),
|
||||||
|
$request->getRequestTarget(),
|
||||||
|
$request->getProtocolVersion()
|
||||||
|
);
|
||||||
|
$headers = $this->formatHeaders($request);
|
||||||
|
$contents = $request->getBody()->getContents();
|
||||||
|
return sprintf("%s%s\r\n%s", $status, $headers, $contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
*/
|
||||||
|
public function response(ResponseInterface $response): string
|
||||||
|
{
|
||||||
|
$status = $this->wrap(
|
||||||
|
'HTTP/%s %s %s',
|
||||||
|
$response->getProtocolVersion(),
|
||||||
|
$response->getStatusCode(),
|
||||||
|
$response->getReasonPhrase()
|
||||||
|
);
|
||||||
|
$headers = $this->formatHeaders($response);
|
||||||
|
$contents = $response->getBody()->getContents();
|
||||||
|
return sprintf("%s%s\r\n%s", $status, $headers, $contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MessageInterface $message
|
||||||
|
*/
|
||||||
|
public function message(MessageInterface $message): string
|
||||||
|
{
|
||||||
|
if ($message instanceof RequestInterface) {
|
||||||
|
return $this->request($message);
|
||||||
|
} elseif ($message instanceof ResponseInterface) {
|
||||||
|
return $this->response($message);
|
||||||
|
}
|
||||||
|
throw new DomainException(sprintf('Unsupported message type: %s', get_class($message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function formatHeaders(MessageInterface $message): string
|
||||||
|
{
|
||||||
|
$lines = '';
|
||||||
|
foreach ($message->getHeaders() as $name => $values) {
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$lines .= $this->wrap('%s: %s', $name, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function wrap(string $format, string|int ...$values): string
|
||||||
|
{
|
||||||
|
return trim(sprintf($format, ...$values)) . "\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sören Jensen
|
||||||
|
|
||||||
|
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.
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/net-stream",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Socket stream classes implementing PSR-7 Stream and PSR-17 StreamFactory",
|
||||||
|
"homepage": "https://phrity.sirn.se/net-stream",
|
||||||
|
"keywords": ["socket", "stream", "stream factory", "client", "server", "PSR-7", "PSR-17"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Net\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Net\\Test\\": "tests/mock/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"phrity/util-interpolator": "^1.1",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phrity/net-uri": "^2.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context class.
|
||||||
|
*/
|
||||||
|
class Context
|
||||||
|
{
|
||||||
|
/** @var resource */
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
/** @var array<int<1, 10>, Closure> */
|
||||||
|
protected array $notifiers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create exception.
|
||||||
|
* @param open-resource|null $stream
|
||||||
|
* @throws InvalidArgumentException if not a resource
|
||||||
|
* @throws InvalidArgumentException if wrong resource type
|
||||||
|
*/
|
||||||
|
public function __construct(mixed $stream = null)
|
||||||
|
{
|
||||||
|
if (is_null($stream)) {
|
||||||
|
$stream = stream_context_create();
|
||||||
|
}
|
||||||
|
$type = gettype($stream);
|
||||||
|
if ($type !== 'resource') {
|
||||||
|
throw new InvalidArgumentException("Invalid stream provided; got type '{$type}'.");
|
||||||
|
}
|
||||||
|
$rtype = get_resource_type($stream);
|
||||||
|
if (!in_array($rtype, ['stream', 'persistent stream', 'stream-context'])) {
|
||||||
|
throw new InvalidArgumentException("Invalid stream provided; got resource type '{$rtype}'.");
|
||||||
|
}
|
||||||
|
$this->stream = $stream;
|
||||||
|
stream_context_set_params($this->stream, ['notification' => function (...$input) {
|
||||||
|
$this->notifyCallback(...$input);
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOption(string $wrapper, string $option): mixed
|
||||||
|
{
|
||||||
|
return stream_context_get_options($this->stream)[$wrapper][$option] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public function getOptions(): array
|
||||||
|
{
|
||||||
|
return stream_context_get_options($this->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws StreamException on failure
|
||||||
|
*/
|
||||||
|
public function setOption(string $wrapper, string $option, mixed $value): self
|
||||||
|
{
|
||||||
|
if (!is_resource($this->stream) || !stream_context_set_option($this->stream, $wrapper, $option, $value)) {
|
||||||
|
throw new StreamException(StreamException::CONTEXT_SET_ERR);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, mixed>> $options
|
||||||
|
*/
|
||||||
|
public function setOptions(array $options): self
|
||||||
|
{
|
||||||
|
foreach ($options as $wrapper => $wrapperOptions) {
|
||||||
|
foreach ($wrapperOptions as $option => $value) {
|
||||||
|
$this->setOption($wrapper, $option, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use getOption.
|
||||||
|
*/
|
||||||
|
public function getParam(string $param): mixed
|
||||||
|
{
|
||||||
|
return stream_context_get_params($this->stream)[$param] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
* @deprecated Use getOptions.
|
||||||
|
*/
|
||||||
|
public function getParams(): array
|
||||||
|
{
|
||||||
|
return stream_context_get_params($this->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use setOption and on- callbacks instead.
|
||||||
|
*/
|
||||||
|
public function setParam(string $param, mixed $value): self
|
||||||
|
{
|
||||||
|
$this->setParams([$param => $value]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $params
|
||||||
|
* @deprecated Use setOptions and on- callbacks instead.
|
||||||
|
* @throws StreamException on failure
|
||||||
|
*/
|
||||||
|
public function setParams(array $params): self
|
||||||
|
{
|
||||||
|
/** @phpstan-ignore booleanNot.alwaysFalse */
|
||||||
|
if (!is_resource($this->stream) || !stream_context_set_params($this->stream, $params)) {
|
||||||
|
throw new StreamException(StreamException::CONTEXT_SET_ERR);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResource(): mixed
|
||||||
|
{
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(): void $closure */
|
||||||
|
public function onResolve(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_RESOLVE] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(): void $closure */
|
||||||
|
public function onConnect(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_CONNECT] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(): void $closure */
|
||||||
|
public function onAuthRequired(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_AUTH_REQUIRED] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(string $mimeType): void $closure */
|
||||||
|
public function onMimeType(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_MIME_TYPE_IS] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(int $fileSize): void $closure */
|
||||||
|
public function onFileSize(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_FILE_SIZE_IS] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(string $uri): void $closure */
|
||||||
|
public function onRedirected(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_REDIRECTED] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(int $transferred, int $max): void $closure */
|
||||||
|
public function onProgress(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_PROGRESS] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(): void $closure */
|
||||||
|
public function onCompleted(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_COMPLETED] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(string $message, int $code): void $closure */
|
||||||
|
public function onFailure(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_FAILURE] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(): void $closure */
|
||||||
|
public function onAuthResult(Closure $closure): void
|
||||||
|
{
|
||||||
|
$this->notifiers[STREAM_NOTIFY_AUTH_RESULT] = $closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function notifyCallback(
|
||||||
|
int $code,
|
||||||
|
int $severity,
|
||||||
|
string|null $message,
|
||||||
|
int $errorCode,
|
||||||
|
int $transferred,
|
||||||
|
int $max,
|
||||||
|
): void {
|
||||||
|
if (!array_key_exists($code, $this->notifiers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$callback = $this->notifiers[$code];
|
||||||
|
$params = match ($code) {
|
||||||
|
STREAM_NOTIFY_RESOLVE,
|
||||||
|
STREAM_NOTIFY_CONNECT,
|
||||||
|
STREAM_NOTIFY_AUTH_REQUIRED,
|
||||||
|
STREAM_NOTIFY_COMPLETED,
|
||||||
|
STREAM_NOTIFY_AUTH_RESULT => [],
|
||||||
|
STREAM_NOTIFY_MIME_TYPE_IS => ['mimeType' => $message],
|
||||||
|
STREAM_NOTIFY_FILE_SIZE_IS => ['fileSize' => $message],
|
||||||
|
STREAM_NOTIFY_REDIRECTED => ['uri' => $message],
|
||||||
|
STREAM_NOTIFY_PROGRESS => ['transferred' => $transferred, 'max' => $max],
|
||||||
|
STREAM_NOTIFY_FAILURE => ['message' => $message, 'code' => $errorCode],
|
||||||
|
};
|
||||||
|
$callback(...$params);
|
||||||
|
}
|
||||||
|
}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Phrity\Util\ErrorHandler;
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phrity\Net\SocketClient class.
|
||||||
|
*/
|
||||||
|
class SocketClient
|
||||||
|
{
|
||||||
|
protected UriInterface $uri;
|
||||||
|
protected ErrorHandler $handler;
|
||||||
|
protected bool $persistent = false;
|
||||||
|
/** @var int<0, max>|float|null */
|
||||||
|
protected int|float|null $timeout = null;
|
||||||
|
protected Context $context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new socker server instance
|
||||||
|
* @param UriInterface $uri The URI to open socket on.
|
||||||
|
*/
|
||||||
|
public function __construct(UriInterface $uri, Context|null $context = null)
|
||||||
|
{
|
||||||
|
$this->uri = $uri;
|
||||||
|
$this->context = $context ?? new Context();
|
||||||
|
$this->handler = new ErrorHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Configuration ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set stream context.
|
||||||
|
* @param Context|array<string, array<string, mixed>>|null $options
|
||||||
|
* @param array<string, mixed>|null $params
|
||||||
|
* @return SocketClient
|
||||||
|
*/
|
||||||
|
public function setContext(Context|array|null $options = null, array|null $params = null): self
|
||||||
|
{
|
||||||
|
if ($options instanceof Context) {
|
||||||
|
$this->context = $options;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// @deprecated
|
||||||
|
// @todo Add deprecation warning
|
||||||
|
$this->context->setOptions($options ?? []);
|
||||||
|
$this->context->setParams($params ?? []);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContext(): Context
|
||||||
|
{
|
||||||
|
return $this->context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set connection persistency.
|
||||||
|
* @param bool $persistent
|
||||||
|
* @return SocketClient
|
||||||
|
*/
|
||||||
|
public function setPersistent(bool $persistent): self
|
||||||
|
{
|
||||||
|
$this->persistent = $persistent;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set timeout in seconds.
|
||||||
|
* @param int<0, max>|float|null $timeout
|
||||||
|
* @return SocketClient
|
||||||
|
* @throws InvalidArgumentException if invalid timeout
|
||||||
|
*/
|
||||||
|
public function setTimeout(int|float|null $timeout): self
|
||||||
|
{
|
||||||
|
if (!is_null($timeout) && $timeout < 0) {
|
||||||
|
throw new InvalidArgumentException("Timeout must be 0 or more.");
|
||||||
|
}
|
||||||
|
$this->timeout = $timeout;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Operations ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a connection on remote socket.
|
||||||
|
* @return SocketStream The stream for opened conenction.
|
||||||
|
*/
|
||||||
|
public function connect(): SocketStream
|
||||||
|
{
|
||||||
|
/** @throws StreamException if connection could not be created */
|
||||||
|
$stream = $this->handler->with(function () {
|
||||||
|
$error_code = $error_message = '';
|
||||||
|
return stream_socket_client(
|
||||||
|
$this->uri->__toString(),
|
||||||
|
$error_code,
|
||||||
|
$error_message,
|
||||||
|
$this->timeout,
|
||||||
|
$this->persistent ? STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT : STREAM_CLIENT_CONNECT,
|
||||||
|
$this->context->getResource()
|
||||||
|
);
|
||||||
|
}, new StreamException(StreamException::CLIENT_CONNECT_ERR, ['uri' => $this->uri]));
|
||||||
|
return new SocketStream($stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use ErrorException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Phrity\Util\ErrorHandler;
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SocketServer class.
|
||||||
|
*/
|
||||||
|
class SocketServer extends Stream
|
||||||
|
{
|
||||||
|
/** @var array<string> */
|
||||||
|
private static array $internet_schemes = ['tcp', 'udp', 'tls', 'ssl'];
|
||||||
|
/** @var array<string> */
|
||||||
|
private static array $unix_schemes = ['unix', 'udg'];
|
||||||
|
|
||||||
|
protected ErrorHandler $handler;
|
||||||
|
protected string $address;
|
||||||
|
/** @var resource */
|
||||||
|
protected $stream;
|
||||||
|
protected Context $context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new socker server instance
|
||||||
|
* @param UriInterface $uri The URI to open socket on.
|
||||||
|
* @throws StreamException if invalid scheme.
|
||||||
|
* @throws StreamException if unsupported scheme.
|
||||||
|
* @throws StreamException if unable to create socket.
|
||||||
|
*/
|
||||||
|
public function __construct(UriInterface $uri, Context|null $context = null)
|
||||||
|
{
|
||||||
|
$this->handler = new ErrorHandler();
|
||||||
|
if (!in_array($uri->getScheme(), $this->getTransports())) {
|
||||||
|
throw new StreamException(StreamException::SCHEME_TRANSPORT, ['scheme' => $uri->getScheme()]);
|
||||||
|
}
|
||||||
|
if (in_array(substr($uri->getScheme(), 0, 3), self::$internet_schemes)) {
|
||||||
|
$this->address = "{$uri->getScheme()}://{$uri->getAuthority()}";
|
||||||
|
} elseif (in_array($uri->getScheme(), self::$unix_schemes)) {
|
||||||
|
$this->address = "{$uri->getScheme()}://{$uri->getPath()}";
|
||||||
|
} else {
|
||||||
|
throw new StreamException(StreamException::SCHEME_HANDLER, ['scheme' => $uri->getScheme()]);
|
||||||
|
}
|
||||||
|
$this->context = $context ?? new Context();
|
||||||
|
/** @throws StreamException on failure */
|
||||||
|
$this->stream = $this->handler->with(function () {
|
||||||
|
$error_code = $error_message = '';
|
||||||
|
return stream_socket_server(
|
||||||
|
$this->address,
|
||||||
|
$error_code,
|
||||||
|
$error_message,
|
||||||
|
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
|
||||||
|
$this->context->getResource()
|
||||||
|
);
|
||||||
|
}, new StreamException(StreamException::SERVER_SOCKET_ERR, ['uri' => $uri->__toString()]));
|
||||||
|
$this->evalStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Configuration ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set stream context.
|
||||||
|
* @param Context|array<string, array<string, mixed>>|null $options
|
||||||
|
* @param array<string, mixed>|null $params
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setContext(Context|array|null $options = null, array|null $params = null): self
|
||||||
|
{
|
||||||
|
if ($options instanceof Context) {
|
||||||
|
$this->context = $options;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// @deprecated
|
||||||
|
// @todo Add deprecation warning
|
||||||
|
$this->context->setOptions($options ?? []);
|
||||||
|
$this->context->setParams($params ?? []);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContext(): Context
|
||||||
|
{
|
||||||
|
return $this->context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve list of registered socket transports.
|
||||||
|
* @return array<string> List of registered transports.
|
||||||
|
*/
|
||||||
|
public function getTransports(): array
|
||||||
|
{
|
||||||
|
return stream_get_transports();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If server is in blocking mode.
|
||||||
|
* @return bool|null
|
||||||
|
*/
|
||||||
|
public function isBlocking(): bool|null
|
||||||
|
{
|
||||||
|
return $this->getMetadata('blocked');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle blocking/non-blocking mode.
|
||||||
|
* @param bool $enable Blocking mode to set.
|
||||||
|
* @return bool If operation was succesful.
|
||||||
|
* @throws StreamException if socket is closed.
|
||||||
|
*/
|
||||||
|
public function setBlocking(bool $enable): bool
|
||||||
|
{
|
||||||
|
if (!is_resource($this->stream)) {
|
||||||
|
throw new StreamException(StreamException::SERVER_CLOSED);
|
||||||
|
}
|
||||||
|
return stream_set_blocking($this->stream, $enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stream metadata as an associative array or retrieve a specific key.
|
||||||
|
* @param string $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|null $key = null): mixed
|
||||||
|
{
|
||||||
|
if (!is_resource($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Add URI default for version compability
|
||||||
|
$meta = array_merge([
|
||||||
|
'uri' => $this->address,
|
||||||
|
], stream_get_meta_data($this->stream));
|
||||||
|
if (isset($key)) {
|
||||||
|
return array_key_exists($key, $meta) ? $meta[$key] : null;
|
||||||
|
}
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Operations ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept a connection on a socket.
|
||||||
|
* @param int<0, max>|float|null $timeout Override the default socket accept timeout.
|
||||||
|
* @return SocketStream|null The stream for opened conenction.
|
||||||
|
* @throws InvalidArgumentException if invalid timeout
|
||||||
|
* @throws StreamException if socket is closed
|
||||||
|
*/
|
||||||
|
public function accept(int|float|null $timeout = null): SocketStream|null
|
||||||
|
{
|
||||||
|
if (!is_null($timeout) && $timeout < 0) {
|
||||||
|
throw new InvalidArgumentException("Timeout must be 0 or more.");
|
||||||
|
}
|
||||||
|
if (!is_resource($this->stream)) {
|
||||||
|
throw new StreamException(StreamException::SERVER_CLOSED);
|
||||||
|
}
|
||||||
|
/** @throws StreamException */
|
||||||
|
$stream = $this->handler->with(function () use ($timeout) {
|
||||||
|
$peer_name = '';
|
||||||
|
return stream_socket_accept($this->stream, $timeout, $peer_name);
|
||||||
|
}, function (ErrorException $e) {
|
||||||
|
// If non-blocking mode, don't throw error on time out
|
||||||
|
if ($this->getMetadata('blocked') === false && substr_count($e->getMessage(), 'timed out') > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new StreamException(StreamException::SERVER_ACCEPT_ERR, [], $e);
|
||||||
|
});
|
||||||
|
return $stream ? new SocketStream($stream) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SocketStream class.
|
||||||
|
*/
|
||||||
|
class SocketStream extends Stream
|
||||||
|
{
|
||||||
|
// ---------- Configuration ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If stream is connected.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isConnected(): bool
|
||||||
|
{
|
||||||
|
return is_resource($this->stream) && ($this->readable || $this->writable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name of remote socket, or null if not connected.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getRemoteName(): string|null
|
||||||
|
{
|
||||||
|
return is_resource($this->stream) ? (stream_socket_get_name($this->stream, true) ?: null) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name of local socket, or null if not connected.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getLocalName(): string|null
|
||||||
|
{
|
||||||
|
return is_resource($this->stream) ? (stream_socket_get_name($this->stream, false) ?: null) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get type of stream resoucre.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getResourceType(): string
|
||||||
|
{
|
||||||
|
return $this->stream ? get_resource_type($this->stream) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If stream is in blocking mode.
|
||||||
|
* @return bool|null
|
||||||
|
*/
|
||||||
|
public function isBlocking(): bool|null
|
||||||
|
{
|
||||||
|
return $this->getMetadata('blocked');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle blocking/non-blocking mode.
|
||||||
|
* @param bool $enable Blocking mode to set.
|
||||||
|
* @return bool If operation was succesful.
|
||||||
|
* @throws StreamException if stream is closed.
|
||||||
|
*/
|
||||||
|
public function setBlocking(bool $enable): bool
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new StreamException(StreamException::STREAM_DETACHED);
|
||||||
|
}
|
||||||
|
return stream_set_blocking($this->stream, $enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If socket stream has unread content.
|
||||||
|
* @return bool If there is content to read.
|
||||||
|
* @throws StreamException if stream is unselectable.
|
||||||
|
*/
|
||||||
|
public function hasContents(): bool
|
||||||
|
{
|
||||||
|
if (!is_resource($this->stream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/** @throws StreamException */
|
||||||
|
return $this->handler->with(function () {
|
||||||
|
$read = [$this->getOpenResource()];
|
||||||
|
$write = $oob = [];
|
||||||
|
return stream_select($read, $write, $oob, 0, 0) > 0;
|
||||||
|
}, new StreamException(StreamException::FAIL_SELECT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set timeout period on a stream.
|
||||||
|
* @param int<0, max>|float $timeout Seconds to be set.
|
||||||
|
* @param int|null $microseconds Microseconds to be set - deprecated
|
||||||
|
* @return bool If operation was succesful.
|
||||||
|
* @throws InvalidArgumentException if invalid timeout.
|
||||||
|
* @throws StreamException if stream is closed.
|
||||||
|
*/
|
||||||
|
public function setTimeout(int|float $timeout, int|null $microseconds = null): bool
|
||||||
|
{
|
||||||
|
// @deprecated Setting $microseconds is deprecated, use float value on $timeout instead
|
||||||
|
// @todo Add deprecation warning
|
||||||
|
if ($timeout < 0) {
|
||||||
|
throw new InvalidArgumentException("Timeout must be 0 or more.");
|
||||||
|
}
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new StreamException(StreamException::STREAM_DETACHED);
|
||||||
|
}
|
||||||
|
$seconds = intval($timeout);
|
||||||
|
$microseconds = $microseconds ?? intval(round($timeout - $seconds, 6) * 1000000);
|
||||||
|
return stream_set_timeout($this->stream, $seconds, $microseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Operations ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read line from the stream.
|
||||||
|
* @param int<0, max> $length Read up to $length bytes from the object and return them.
|
||||||
|
* @return string|null Returns the data read from the stream, or null of eof.
|
||||||
|
* @throws StreamException if an error occurs.
|
||||||
|
*/
|
||||||
|
public function readLine(int $length): string|null
|
||||||
|
{
|
||||||
|
$stream = $this->getOpenResource();
|
||||||
|
if (!$this->readable) {
|
||||||
|
throw new StreamException(StreamException::NOT_READABLE);
|
||||||
|
}
|
||||||
|
/** @throws StreamException */
|
||||||
|
return $this->handler->with(function () use ($stream, $length) {
|
||||||
|
$result = fgets($stream, $length);
|
||||||
|
return $result === false ? null : $result;
|
||||||
|
}, new StreamException(StreamException::FAIL_GETS));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream for further reading.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function closeRead(): void
|
||||||
|
{
|
||||||
|
if (is_resource($this->stream)) {
|
||||||
|
if ($this->readable && $this->writable) {
|
||||||
|
stream_socket_shutdown($this->stream, STREAM_SHUT_RD);
|
||||||
|
$this->evalStream();
|
||||||
|
} elseif (!$this->writable) {
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->readable = false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Closes the stream for further writing.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function closeWrite(): void
|
||||||
|
{
|
||||||
|
if ($this->readable && $this->writable) {
|
||||||
|
stream_socket_shutdown($this->getOpenResource(), STREAM_SHUT_WR);
|
||||||
|
$this->evalStream();
|
||||||
|
} elseif (!$this->readable) {
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
$this->writable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+316
@@ -0,0 +1,316 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Phrity\Util\ErrorHandler;
|
||||||
|
use Stringable;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phrity\Net\Stream class.
|
||||||
|
* @see https://www.php-fig.org/psr/psr-7/#34-psrhttpmessagestreaminterface
|
||||||
|
*/
|
||||||
|
class Stream implements StreamInterface, Stringable
|
||||||
|
{
|
||||||
|
/** @var array<string> */
|
||||||
|
private static array $readmodes = ['r', 'r+', 'w+', 'a+', 'x+', 'c+'];
|
||||||
|
/** @var array<string> */
|
||||||
|
private static array $writemodes = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
|
||||||
|
|
||||||
|
/** @var resource|null */
|
||||||
|
protected $stream;
|
||||||
|
protected Context $context;
|
||||||
|
protected ErrorHandler $handler;
|
||||||
|
protected bool $readable = false;
|
||||||
|
protected bool $writable = false;
|
||||||
|
protected bool $seekable = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new stream wrapper instance
|
||||||
|
* @param resource $stream A stream resource to wrap
|
||||||
|
* @throws InvalidArgumentException If not a valid stream resource
|
||||||
|
*/
|
||||||
|
public function __construct($stream)
|
||||||
|
{
|
||||||
|
$type = gettype($stream);
|
||||||
|
if ($type !== 'resource') {
|
||||||
|
throw new InvalidArgumentException("Invalid stream provided; got type '{$type}'.");
|
||||||
|
}
|
||||||
|
$rtype = get_resource_type($stream);
|
||||||
|
if (!in_array($rtype, ['stream', 'persistent stream'])) {
|
||||||
|
throw new InvalidArgumentException("Invalid stream provided; got resource type '{$rtype}'.");
|
||||||
|
}
|
||||||
|
$this->stream = $stream;
|
||||||
|
$this->context = new Context($this->stream);
|
||||||
|
$this->handler = new ErrorHandler();
|
||||||
|
$this->evalStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- PSR-7 methods ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream and any underlying resources.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
if (is_resource($this->stream)) {
|
||||||
|
fclose($this->stream);
|
||||||
|
}
|
||||||
|
$this->stream = null;
|
||||||
|
$this->evalStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separates any underlying resources from the stream.
|
||||||
|
* After the stream has been detached, the stream is in an unusable state.
|
||||||
|
* @return resource|null Underlying stream, if any
|
||||||
|
*/
|
||||||
|
public function detach(): mixed
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$stream = $this->stream;
|
||||||
|
$this->stream = null;
|
||||||
|
$this->evalStream();
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stream metadata as an associative array or retrieve a specific key.
|
||||||
|
* @param string $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|null $key = null): mixed
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$meta = stream_get_meta_data($this->stream);
|
||||||
|
if (isset($key)) {
|
||||||
|
return array_key_exists($key, $meta) ? $meta[$key] : null;
|
||||||
|
}
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current position of the file read/write pointer
|
||||||
|
* @return int Position of the file pointer
|
||||||
|
* @throws StreamException on error.
|
||||||
|
*/
|
||||||
|
public function tell(): int
|
||||||
|
{
|
||||||
|
/** @throws StreamException */
|
||||||
|
return $this->handler->with(function () {
|
||||||
|
return ftell($this->getOpenResource());
|
||||||
|
}, new StreamException(StreamException::FAIL_TELL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the stream is at the end of the stream.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function eof(): bool
|
||||||
|
{
|
||||||
|
return empty($this->stream) || feof($this->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data from the stream.
|
||||||
|
* @param int<1, max> $length Read up to $length bytes from the object and return them.
|
||||||
|
* @return string Returns the data read from the stream, or an empty string.
|
||||||
|
* @throws StreamException if an error occurs.
|
||||||
|
*/
|
||||||
|
public function read(int $length): string
|
||||||
|
{
|
||||||
|
if ($length < 1) {
|
||||||
|
throw new InvalidArgumentException("Must read minimum 1 byte");
|
||||||
|
}
|
||||||
|
$stream = $this->getOpenResource();
|
||||||
|
if (!$this->readable) {
|
||||||
|
throw new StreamException(StreamException::NOT_READABLE);
|
||||||
|
}
|
||||||
|
/** @throws StreamException */
|
||||||
|
return $this->handler->with(function () use ($stream, $length) {
|
||||||
|
return (string)fread($stream, $length);
|
||||||
|
}, new StreamException(StreamException::FAIL_READ));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 StreamException on failure.
|
||||||
|
*/
|
||||||
|
public function write(string $string): int
|
||||||
|
{
|
||||||
|
$stream = $this->getOpenResource();
|
||||||
|
if (!$this->writable) {
|
||||||
|
throw new StreamException(StreamException::NOT_WRITABLE);
|
||||||
|
}
|
||||||
|
/** @throws StreamException */
|
||||||
|
return $this->handler->with(function () use ($stream, $string) {
|
||||||
|
return fwrite($stream, $string);
|
||||||
|
}, new StreamException(StreamException::FAIL_WRITE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(): int|null
|
||||||
|
{
|
||||||
|
if (!is_resource($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$stats = fstat($this->stream);
|
||||||
|
return $stats['size'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the stream is seekable.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSeekable(): bool
|
||||||
|
{
|
||||||
|
return $this->seekable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seek to a position in the stream.
|
||||||
|
* @param int $offset Stream offset
|
||||||
|
* @param int $whence Specifies how the cursor position will be calculated based on the seek offset.
|
||||||
|
* @throws StreamException on failure.
|
||||||
|
*/
|
||||||
|
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||||
|
{
|
||||||
|
$stream = $this->getOpenResource();
|
||||||
|
if (!$this->seekable) {
|
||||||
|
throw new StreamException(StreamException::NOT_SEEKABLE);
|
||||||
|
}
|
||||||
|
$result = fseek($stream, $offset, $whence);
|
||||||
|
if ($result !== 0) {
|
||||||
|
throw new StreamException(StreamException::FAIL_SEEK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the stream is writable.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return $this->writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the stream is readable.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return $this->readable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the remaining contents in a string
|
||||||
|
* @return string
|
||||||
|
* @throws StreamException if unable to read.
|
||||||
|
* @throws StreamException if error occurs while reading.
|
||||||
|
*/
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
$stream = $this->getOpenResource();
|
||||||
|
if (!$this->readable) {
|
||||||
|
throw new StreamException(StreamException::NOT_READABLE);
|
||||||
|
}
|
||||||
|
/** @throws StreamException */
|
||||||
|
return $this->handler->with(function () use ($stream) {
|
||||||
|
return stream_get_contents($stream);
|
||||||
|
}, new StreamException(StreamException::FAIL_CONTENTS));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all data from the stream into a string, from the beginning to end.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->isSeekable()) {
|
||||||
|
$this->rewind();
|
||||||
|
}
|
||||||
|
return $this->getContents();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
trigger_error($e->getMessage(), E_USER_WARNING);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- StreamInterface methods -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return context for stream.
|
||||||
|
* @return Context
|
||||||
|
*/
|
||||||
|
public function getContext(): Context
|
||||||
|
{
|
||||||
|
return $this->context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return underlying resource.
|
||||||
|
* @return resource|null
|
||||||
|
*/
|
||||||
|
public function getResource(): mixed
|
||||||
|
{
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Protected helper methods ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate stream state.
|
||||||
|
*/
|
||||||
|
protected function evalStream(): void
|
||||||
|
{
|
||||||
|
if ($this->stream && $meta = $this->getMetadata()) {
|
||||||
|
$mode = substr($meta['mode'], 0, 2);
|
||||||
|
$this->readable = in_array($mode, self::$readmodes);
|
||||||
|
$this->writable = in_array($mode, self::$writemodes);
|
||||||
|
$this->seekable = $meta['seekable'];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->readable = $this->writable = $this->seekable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return underlying resource.
|
||||||
|
* @return resource
|
||||||
|
* @throws StreamException if closed.
|
||||||
|
*/
|
||||||
|
protected function getOpenResource(): mixed
|
||||||
|
{
|
||||||
|
if (!is_resource($this->stream)) {
|
||||||
|
throw new StreamException(StreamException::STREAM_DETACHED);
|
||||||
|
}
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use Countable;
|
||||||
|
use ErrorException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Iterator;
|
||||||
|
use Phrity\Util\ErrorHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StreamCollection class.
|
||||||
|
* @implements Iterator<string, Stream>
|
||||||
|
*/
|
||||||
|
class StreamCollection implements Countable, Iterator
|
||||||
|
{
|
||||||
|
protected ErrorHandler $handler;
|
||||||
|
/** @var array<string, StreamInterface|StreamContainerInterface> */
|
||||||
|
private array $streams = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new stream collection instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->handler = new ErrorHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Collectors and selectors ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach stream to collection.
|
||||||
|
* @param StreamInterface|StreamContainerInterface $attach Stream to attach.
|
||||||
|
* @param string|null $key Definable name of stream.
|
||||||
|
* @return string Name of stream.
|
||||||
|
* @throws StreamException If already attached.
|
||||||
|
*/
|
||||||
|
public function attach(StreamInterface|StreamContainerInterface $attach, string|null $key = null): string
|
||||||
|
{
|
||||||
|
if ($key && array_key_exists($key, $this->streams)) {
|
||||||
|
throw new StreamException(StreamException::COLLECT_KEY_CONFLICT, ['key' => $key]);
|
||||||
|
}
|
||||||
|
$key = $key ?: $this->createKey();
|
||||||
|
$this->streams[$key] = $attach;
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach stream from collection.
|
||||||
|
* @param StreamInterface|StreamContainerInterface|string $detach Stream or name of stream to detach.
|
||||||
|
* @return bool If a stream was detached.
|
||||||
|
*/
|
||||||
|
public function detach(StreamInterface|StreamContainerInterface|string $detach): bool
|
||||||
|
{
|
||||||
|
if (is_string($detach)) {
|
||||||
|
if (array_key_exists($detach, $this->streams)) {
|
||||||
|
unset($this->streams[$detach]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($detach instanceof Stream) {
|
||||||
|
foreach ($this->streams as $key => $stream) {
|
||||||
|
if ($stream === $detach) {
|
||||||
|
unset($this->streams[$key]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all readable streams into new collection.
|
||||||
|
* @return self New collection instance.
|
||||||
|
*/
|
||||||
|
public function getReadable(): self
|
||||||
|
{
|
||||||
|
$readables = new self();
|
||||||
|
foreach ($this->streams as $key => $stream) {
|
||||||
|
if ($this->getStream($stream)->isReadable()) {
|
||||||
|
$readables->attach($stream, $key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $readables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all writable streams into new collection.
|
||||||
|
* @return self New collection instance.
|
||||||
|
*/
|
||||||
|
public function getWritable(): self
|
||||||
|
{
|
||||||
|
$writables = new self();
|
||||||
|
foreach ($this->streams as $key => $stream) {
|
||||||
|
if ($this->getStream($stream)->isWritable()) {
|
||||||
|
$writables->attach($stream, $key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $writables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for redable content in stream collection.
|
||||||
|
* @param int<0, max>|float $timeout Timeout in seconds.
|
||||||
|
* @return self New collection instance.
|
||||||
|
* @throws InvalidArgumentException If invalid timeout.
|
||||||
|
*/
|
||||||
|
public function waitRead(int|float $timeout = 60): self
|
||||||
|
{
|
||||||
|
if ($timeout < 0) {
|
||||||
|
throw new InvalidArgumentException("Timeout must be 0 or more.");
|
||||||
|
}
|
||||||
|
$seconds = intval($timeout);
|
||||||
|
$microseconds = intval(round($timeout - $seconds, 6) * 1000000);
|
||||||
|
|
||||||
|
$read = [];
|
||||||
|
foreach ($this->streams as $key => $stream) {
|
||||||
|
if ($this->getStream($stream)->isReadable()) {
|
||||||
|
$read[$key] = $this->getStream($stream)->getResource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($read)) {
|
||||||
|
return new self(); // Nothing to select
|
||||||
|
}
|
||||||
|
|
||||||
|
$changed = $this->handler->with(function () use ($read, $seconds, $microseconds) {
|
||||||
|
$write = $oob = [];
|
||||||
|
/** @phpstan-ignore argument.type */
|
||||||
|
stream_select($read, $write, $oob, $seconds, $microseconds);
|
||||||
|
return $read;
|
||||||
|
}, function (ErrorException $error) {
|
||||||
|
return []; // Ignore, but don't use result
|
||||||
|
});
|
||||||
|
|
||||||
|
$ready = new self();
|
||||||
|
foreach ($changed as $key => $resource) {
|
||||||
|
$ready->attach($this->streams[$key], $key);
|
||||||
|
}
|
||||||
|
return $ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Countable interface implementation ------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count contained streams.
|
||||||
|
* @return int Number of streams in collection.
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return count($this->streams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Iterator interface implementation -------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current stream.
|
||||||
|
* @return StreamInterface|StreamContainerInterface|null Current stream.
|
||||||
|
*/
|
||||||
|
public function current(): StreamInterface|StreamContainerInterface|null
|
||||||
|
{
|
||||||
|
return current($this->streams) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the key of the current stream.
|
||||||
|
* @return string Current key.
|
||||||
|
*/
|
||||||
|
public function key(): string|null
|
||||||
|
{
|
||||||
|
return key($this->streams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move forward to next stream.
|
||||||
|
*/
|
||||||
|
public function next(): void
|
||||||
|
{
|
||||||
|
next($this->streams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind the Iterator to the first stream.
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
reset($this->streams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if current position is valid.
|
||||||
|
* @return bool True if valid.
|
||||||
|
*/
|
||||||
|
public function valid(): bool
|
||||||
|
{
|
||||||
|
return array_key_exists(key($this->streams) ?? -1, $this->streams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Protected helper methods ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create unique key.
|
||||||
|
* @return string Unique key.
|
||||||
|
*/
|
||||||
|
protected function createKey(): string
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
$key = bin2hex(random_bytes(16));
|
||||||
|
} while (array_key_exists($key, $this->streams));
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getStream(StreamInterface|StreamContainerInterface $stream): StreamInterface
|
||||||
|
{
|
||||||
|
return $stream instanceof StreamInterface ? $stream : $stream->getStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StreamContainerInterface.
|
||||||
|
*/
|
||||||
|
interface StreamContainerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return StreamInterface
|
||||||
|
*/
|
||||||
|
public function getStream(): StreamInterface;
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use Phrity\Util\Interpolator\InterpolatorTrait;
|
||||||
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StreamException class.
|
||||||
|
*/
|
||||||
|
class StreamException extends RuntimeException
|
||||||
|
{
|
||||||
|
use InterpolatorTrait;
|
||||||
|
|
||||||
|
// Stream errors
|
||||||
|
public const STREAM_DETACHED = 1000;
|
||||||
|
public const NOT_READABLE = 1010;
|
||||||
|
public const NOT_WRITABLE = 1011;
|
||||||
|
public const NOT_SEEKABLE = 1012;
|
||||||
|
public const FAIL_READ = 1020;
|
||||||
|
public const FAIL_WRITE = 1021;
|
||||||
|
public const FAIL_SEEK = 1022;
|
||||||
|
public const FAIL_TELL = 1023;
|
||||||
|
public const FAIL_CONTENTS = 1024;
|
||||||
|
public const FAIL_GETS = 1025;
|
||||||
|
public const FAIL_SELECT = 1026;
|
||||||
|
|
||||||
|
// Client errors
|
||||||
|
public const CLIENT_CONNECT_ERR = 2000;
|
||||||
|
|
||||||
|
// Server errors
|
||||||
|
public const SCHEME_TRANSPORT = 3000;
|
||||||
|
public const SCHEME_HANDLER = 3001;
|
||||||
|
public const SERVER_SOCKET_ERR = 3010;
|
||||||
|
public const SERVER_CLOSED = 3011;
|
||||||
|
public const SERVER_ACCEPT_ERR = 3012;
|
||||||
|
|
||||||
|
// Collection errors
|
||||||
|
public const COLLECT_KEY_CONFLICT = 4000;
|
||||||
|
public const COLLECT_SELECT_ERR = 4001;
|
||||||
|
|
||||||
|
// Context errors
|
||||||
|
public const CONTEXT_SET_ERR = 5000;
|
||||||
|
|
||||||
|
/** @var array<int, string> */
|
||||||
|
private static array $messages = [
|
||||||
|
self::STREAM_DETACHED => 'Stream is detached.',
|
||||||
|
self::NOT_READABLE => 'Stream is not readable.',
|
||||||
|
self::NOT_WRITABLE => 'Stream is not writable.',
|
||||||
|
self::NOT_SEEKABLE => 'Stream is not seekable.',
|
||||||
|
self::FAIL_READ => 'Failed read() on stream.',
|
||||||
|
self::FAIL_WRITE => 'Failed write() on stream.',
|
||||||
|
self::FAIL_SEEK => 'Failed seek() on stream.',
|
||||||
|
self::FAIL_TELL => 'Failed tell() on stream.',
|
||||||
|
self::FAIL_CONTENTS => 'Failed getContents() on stream.',
|
||||||
|
self::FAIL_GETS => 'Failed gets() on stream.',
|
||||||
|
self::FAIL_SELECT => 'Failed select() on stream.',
|
||||||
|
self::CLIENT_CONNECT_ERR => 'Client could not connect to "{uri}".',
|
||||||
|
self::SCHEME_TRANSPORT => 'Scheme "{scheme}" is not supported.',
|
||||||
|
self::SCHEME_HANDLER => 'Could not handle scheme "{scheme}".',
|
||||||
|
self::SERVER_SOCKET_ERR => 'Could not create socket for "{uri}".',
|
||||||
|
self::SERVER_CLOSED => 'Server is closed.',
|
||||||
|
self::SERVER_ACCEPT_ERR => 'Could not accept on socket.',
|
||||||
|
self::COLLECT_KEY_CONFLICT => 'Stream with name "{key}" already attached.',
|
||||||
|
self::COLLECT_SELECT_ERR => 'Failed to select streams for reading.',
|
||||||
|
self::CONTEXT_SET_ERR => 'Failed to set option/param on context.',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create exception.
|
||||||
|
* @param int $code Error code
|
||||||
|
* @param array<string, mixed> $data Additional data
|
||||||
|
* @param Throwable|null $previous Previous exception
|
||||||
|
*/
|
||||||
|
public function __construct(int $code, array $data = [], Throwable|null $previous = null)
|
||||||
|
{
|
||||||
|
$message = self::$messages[$code];
|
||||||
|
$message = $this->interpolate($message, $data);
|
||||||
|
if ($previous) {
|
||||||
|
$message .= " ({$previous->getMessage()})";
|
||||||
|
}
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Phrity\Util\ErrorHandler;
|
||||||
|
use Psr\Http\Message\{
|
||||||
|
StreamFactoryInterface,
|
||||||
|
UriInterface
|
||||||
|
};
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StreamFactory class.
|
||||||
|
* @see https://www.php-fig.org/psr/psr-17/#24-streamfactoryinterface
|
||||||
|
*/
|
||||||
|
class StreamFactory implements StreamFactoryInterface
|
||||||
|
{
|
||||||
|
/** @var array<string> */
|
||||||
|
private static array $modes = ['r', 'r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+', 'e'];
|
||||||
|
|
||||||
|
private ErrorHandler $handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new stream wrapper instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->handler = new ErrorHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- PSR-17 methods --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream from a string.
|
||||||
|
* @param string $content String content with which to populate the stream.
|
||||||
|
* @return Stream A stream instance.
|
||||||
|
*/
|
||||||
|
public function createStream(string $content = ''): Stream
|
||||||
|
{
|
||||||
|
$resource = $this->createResource('php://temp', 'r+');
|
||||||
|
fwrite($resource, $content);
|
||||||
|
return $this->createStreamFromResource($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a stream from an existing file.
|
||||||
|
* @param string $filename The filename or stream URI to use as basis of stream.
|
||||||
|
* @param string $mode The mode with which to open the underlying filename/stream.
|
||||||
|
* @throws InvalidArgumentException If the mode is invalid.
|
||||||
|
* @return Stream A stream instance.
|
||||||
|
*/
|
||||||
|
public function createStreamFromFile(string $filename, string $mode = 'r'): Stream
|
||||||
|
{
|
||||||
|
if (!in_array($mode, self::$modes)) {
|
||||||
|
throw new InvalidArgumentException("Invalid mode '{$mode}'.");
|
||||||
|
}
|
||||||
|
$resource = $this->createResource($filename, $mode);
|
||||||
|
return $this->createStreamFromResource($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream from an existing resource.
|
||||||
|
* The stream MUST be readable and may be writable.
|
||||||
|
* @param resource $resource The PHP resource to use as the basis for the stream.
|
||||||
|
* @return Stream A stream instance.
|
||||||
|
*/
|
||||||
|
public function createStreamFromResource($resource): Stream
|
||||||
|
{
|
||||||
|
return new Stream($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Extensions ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new socket client.
|
||||||
|
* @param UriInterface $uri The URI to connect to.
|
||||||
|
* @return SocketClient A socket client instance.
|
||||||
|
*/
|
||||||
|
public function createSocketClient(UriInterface $uri, Context|null $context = null): SocketClient
|
||||||
|
{
|
||||||
|
return new SocketClient($uri, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new socket server.
|
||||||
|
* @param UriInterface $uri The URI to create server on.
|
||||||
|
* @return SocketServer A socket server instance.
|
||||||
|
*/
|
||||||
|
public function createSocketServer(UriInterface $uri, Context|null $context = null): SocketServer
|
||||||
|
{
|
||||||
|
return new SocketServer($uri, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ocket stream from an existing resource.
|
||||||
|
* The stream MUST be readable and may be writable.
|
||||||
|
* @param resource $resource The PHP resource to use as the basis for the stream.
|
||||||
|
* @return SocketStream A socket stream instance.
|
||||||
|
*/
|
||||||
|
public function createSocketStreamFromResource($resource): SocketStream
|
||||||
|
{
|
||||||
|
return new SocketStream($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream collection.
|
||||||
|
* @return StreamCollection A stream collection.
|
||||||
|
*/
|
||||||
|
public function createStreamCollection(): StreamCollection
|
||||||
|
{
|
||||||
|
return new StreamCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Helpers ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
* @throws RuntimeException If fails to open resource
|
||||||
|
*/
|
||||||
|
private function createResource(string $filename, string $mode)
|
||||||
|
{
|
||||||
|
/** @throws RuntimeException */
|
||||||
|
return $this->handler->with(function () use ($filename, $mode) {
|
||||||
|
/** @var resource $resource */
|
||||||
|
$resource = fopen($filename, $mode);
|
||||||
|
return $resource;
|
||||||
|
}, new RuntimeException("Could not open '{$filename}'."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface as PsrStreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StreamInterface.
|
||||||
|
*/
|
||||||
|
interface StreamInterface extends PsrStreamInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Context
|
||||||
|
*/
|
||||||
|
public function getContext(): Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|null
|
||||||
|
*/
|
||||||
|
public function getResource(): mixed;
|
||||||
|
}
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sören Jensen
|
||||||
|
|
||||||
|
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.
|
||||||
Vendored
+37
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/net-uri",
|
||||||
|
"type": "library",
|
||||||
|
"description": "PSR-7 Uri and PSR-17 UriFactory implementation",
|
||||||
|
"homepage": "https://phrity.sirn.se/net-uri",
|
||||||
|
"keywords": ["uri", "uri factory", "PSR-7", "PSR-17"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Net\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"phrity/comparison": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 | ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "Enables IDN conversion for non-ASCII domains"
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+710
@@ -0,0 +1,710 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for Net\Uri class.
|
||||||
|
* @package Phrity > Net > Uri
|
||||||
|
* @see https://www.rfc-editor.org/rfc/rfc3986
|
||||||
|
* @see https://www.php-fig.org/psr/psr-7/#35-psrhttpmessageuriinterface
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use JsonSerializable;
|
||||||
|
use Phrity\Comparison\{
|
||||||
|
Equalable,
|
||||||
|
IncomparableException,
|
||||||
|
};
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Net\Uri class.
|
||||||
|
*/
|
||||||
|
class Uri implements Equalable, JsonSerializable, Stringable, UriInterface
|
||||||
|
{
|
||||||
|
public const REQUIRE_PORT = 1; // Always include port, explicit or default
|
||||||
|
public const ABSOLUTE_PATH = 2; // Enforce absolute path
|
||||||
|
public const NORMALIZE_PATH = 4; // Normalize path
|
||||||
|
public const IDNA = 8; // @deprecated, replaced by IDN_ENCODE
|
||||||
|
public const IDN_ENCODE = 16; // IDN-encode host
|
||||||
|
public const IDN_DECODE = 32; // IDN-decode host
|
||||||
|
public const URI_DECODE = 64; // Decoded URI
|
||||||
|
public const URI_ENCODE = 128; // Minimal URI encoded
|
||||||
|
public const URI_ENCODE_3986 = 256; // URI encoded RFC 3986
|
||||||
|
|
||||||
|
private const RE_MAIN = '!^(?P<schemec>(?P<scheme>[^:/?#]+):)?(?P<authorityc>//(?P<authority>[^/?#]*))?'
|
||||||
|
. '(?P<path>[^?#]*)(?P<queryc>\?(?P<query>[^#]*))?(?P<fragmentc>#(?P<fragment>.*))?$!';
|
||||||
|
private const RE_AUTH = '!^(?P<userinfoc>(?P<user>[^:/?#]+)(?P<passc>:(?P<pass>[^:/?#]+))?@)?'
|
||||||
|
. '(?P<host>[^:/?#]*|\[[^/?#]*\])(?P<portc>:(?P<port>[0-9]*))?$!';
|
||||||
|
|
||||||
|
/** @var array<string, int> $portDefaults */
|
||||||
|
private static array $portDefaults = [
|
||||||
|
'acap' => 674,
|
||||||
|
'afp' => 548,
|
||||||
|
'dict' => 2628,
|
||||||
|
'dns' => 53,
|
||||||
|
'ftp' => 21,
|
||||||
|
'git' => 9418,
|
||||||
|
'gopher' => 70,
|
||||||
|
'http' => 80,
|
||||||
|
'https' => 443,
|
||||||
|
'imap' => 143,
|
||||||
|
'ipp' => 631,
|
||||||
|
'ipps' => 631,
|
||||||
|
'irc' => 194,
|
||||||
|
'ircs' => 6697,
|
||||||
|
'ldap' => 389,
|
||||||
|
'ldaps' => 636,
|
||||||
|
'mms' => 1755,
|
||||||
|
'msrp' => 2855,
|
||||||
|
'mtqp' => 1038,
|
||||||
|
'nfs' => 111,
|
||||||
|
'nntp' => 119,
|
||||||
|
'nntps' => 563,
|
||||||
|
'pop' => 110,
|
||||||
|
'prospero' => 1525,
|
||||||
|
'redis' => 6379,
|
||||||
|
'rsync' => 873,
|
||||||
|
'rtsp' => 554,
|
||||||
|
'rtsps' => 322,
|
||||||
|
'rtspu' => 5005,
|
||||||
|
'sftp' => 22,
|
||||||
|
'smb' => 445,
|
||||||
|
'snmp' => 161,
|
||||||
|
'ssh' => 22,
|
||||||
|
'svn' => 3690,
|
||||||
|
'telnet' => 23,
|
||||||
|
'ventrilo' => 3784,
|
||||||
|
'vnc' => 5900,
|
||||||
|
'wais' => 210,
|
||||||
|
'ws' => 80,
|
||||||
|
'wss' => 443,
|
||||||
|
];
|
||||||
|
|
||||||
|
private string $scheme = '';
|
||||||
|
private bool $authority = false;
|
||||||
|
private string $host = '';
|
||||||
|
private int|null $port = null;
|
||||||
|
private string $user = '';
|
||||||
|
private string|null $pass = null;
|
||||||
|
private string $path = '';
|
||||||
|
private string $query = '';
|
||||||
|
private string $fragment = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new URI instance using a string
|
||||||
|
* @param string $uriString URI as string
|
||||||
|
* @throws InvalidArgumentException If the given URI cannot be parsed
|
||||||
|
*/
|
||||||
|
public function __construct(string $uriString = '')
|
||||||
|
{
|
||||||
|
$this->parse($uriString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- PSR-7 getters ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the scheme component of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string The URI scheme
|
||||||
|
*/
|
||||||
|
public function getScheme(int $flags = 0): string
|
||||||
|
{
|
||||||
|
return $this->scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the authority component of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string The URI authority, in "[user-info@]host[:port]" format
|
||||||
|
*/
|
||||||
|
public function getAuthority(int $flags = 0): string
|
||||||
|
{
|
||||||
|
$host = $this->formatComponent($this->getHost($flags));
|
||||||
|
if ($host === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$userinfo = $this->formatComponent($this->getUserInfo($flags), '', '@');
|
||||||
|
$port = $this->formatComponent($this->getPort($flags), ':');
|
||||||
|
return "{$userinfo}{$host}{$port}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the user information component of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string The URI user information, in "username[:password]" format
|
||||||
|
*/
|
||||||
|
public function getUserInfo(int $flags = 0): string
|
||||||
|
{
|
||||||
|
$user = $this->formatComponent($this->uriEncode($this->user, $flags));
|
||||||
|
$pass = $this->formatComponent($this->uriEncode($this->pass ?? '', $flags), ':');
|
||||||
|
return $user === '' ? '' : "{$user}{$pass}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the host component of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string The URI host
|
||||||
|
*/
|
||||||
|
public function getHost(int $flags = 0): string
|
||||||
|
{
|
||||||
|
if ($flags & self::IDNA) {
|
||||||
|
trigger_error("Flag IDNA is deprecated; use IDN_ENCODE instead", E_USER_DEPRECATED);
|
||||||
|
return $this->idnEncode($this->host);
|
||||||
|
}
|
||||||
|
if ($flags & self::IDN_ENCODE) {
|
||||||
|
return $this->idnEncode($this->host);
|
||||||
|
}
|
||||||
|
if ($flags & self::IDN_DECODE) {
|
||||||
|
return $this->idnDecode($this->host);
|
||||||
|
}
|
||||||
|
return $this->host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the port component of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return null|int The URI port
|
||||||
|
*/
|
||||||
|
public function getPort(int $flags = 0): int|null
|
||||||
|
{
|
||||||
|
$default = self::$portDefaults[$this->scheme] ?? null;
|
||||||
|
if ($flags & self::REQUIRE_PORT) {
|
||||||
|
return $this->port !== null ? $this->port : $default;
|
||||||
|
}
|
||||||
|
return $this->port === $default ? null : $this->port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the path component of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string The URI path
|
||||||
|
*/
|
||||||
|
public function getPath(int $flags = 0): string
|
||||||
|
{
|
||||||
|
$path = $this->path;
|
||||||
|
if ($flags & self::NORMALIZE_PATH) {
|
||||||
|
$path = $this->normalizePath($path);
|
||||||
|
}
|
||||||
|
if ($flags & self::ABSOLUTE_PATH && substr($path, 0, 1) !== '/') {
|
||||||
|
$path = "/{$path}";
|
||||||
|
}
|
||||||
|
return $this->uriEncode($path, $flags, '\/:@');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the query string of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string The URI query string
|
||||||
|
*/
|
||||||
|
public function getQuery(int $flags = 0): string
|
||||||
|
{
|
||||||
|
return $this->uriEncode($this->query, $flags, '\/:@?');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the fragment component of the URI.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string The URI fragment
|
||||||
|
*/
|
||||||
|
public function getFragment(int $flags = 0): string
|
||||||
|
{
|
||||||
|
return $this->uriEncode($this->fragment, $flags, '\/:@?');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- PSR-7 setters ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified scheme.
|
||||||
|
* @param string $scheme The scheme to use with the new instance
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the specified scheme
|
||||||
|
* @throws InvalidArgumentException for invalid schemes
|
||||||
|
* @throws InvalidArgumentException for unsupported schemes
|
||||||
|
*/
|
||||||
|
public function withScheme(string $scheme, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setScheme($scheme, $flags);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified user information.
|
||||||
|
* @param string $user The user name to use for authority
|
||||||
|
* @param null|string $password The password associated with $user
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the specified user information
|
||||||
|
*/
|
||||||
|
public function withUserInfo(string $user, string|null $password = null, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setUserInfo($user, $password);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified host.
|
||||||
|
* @param string $host The hostname to use with the new instance
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the specified host
|
||||||
|
* @throws InvalidArgumentException for invalid hostnames
|
||||||
|
*/
|
||||||
|
public function withHost(string $host, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setHost($host, $flags);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified port.
|
||||||
|
* @param null|int $port The port to use with the new instance
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the specified port
|
||||||
|
* @throws InvalidArgumentException for invalid ports
|
||||||
|
*/
|
||||||
|
public function withPort(int|null $port, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setPort($port, $flags);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified path.
|
||||||
|
* @param string $path The path to use with the new instance
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the specified path
|
||||||
|
* @throws InvalidArgumentException for invalid paths
|
||||||
|
*/
|
||||||
|
public function withPath(string $path, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setPath($path, $flags);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified query string.
|
||||||
|
* @param string $query The query string to use with the new instance
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the specified query string
|
||||||
|
* @throws InvalidArgumentException for invalid query strings
|
||||||
|
*/
|
||||||
|
public function withQuery(string $query, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setQuery($query, $flags);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified URI fragment.
|
||||||
|
* @param string $fragment The fragment to use with the new instance
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the specified fragment
|
||||||
|
*/
|
||||||
|
public function withFragment(string $fragment, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setFragment($fragment, $flags);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- PSR-7 string & Stringable ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the string representation as a URI reference.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- JsonSerializable ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return JSON encode value as URI reference.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function jsonSerialize(): string
|
||||||
|
{
|
||||||
|
return $this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Equalable ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return JSON encode value as URI reference.
|
||||||
|
* @param UriInterface|string $compareWith
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function equals(mixed $compareWith): bool
|
||||||
|
{
|
||||||
|
if (!$compareWith instanceof UriInterface && !is_string($compareWith)) {
|
||||||
|
throw new IncomparableException(sprintf("Can not compare with type '%s'", get_debug_type($compareWith)));
|
||||||
|
}
|
||||||
|
$flags = self::REQUIRE_PORT | self::NORMALIZE_PATH | self::IDN_ENCODE;
|
||||||
|
$them = $compareWith instanceof self ? $compareWith : new self((string)$compareWith);
|
||||||
|
return $this->toString($flags) == $them->toString($flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Extensions ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the string representation as a URI reference.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @param string $format Optional format specification
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function toString(int $flags = 0, string $format = '{scheme}{authority}{path}{query}{fragment}'): string
|
||||||
|
{
|
||||||
|
$pathFlags = ($this->authority && $this->path ? self::ABSOLUTE_PATH : 0) | $flags;
|
||||||
|
return str_replace([
|
||||||
|
'{scheme}',
|
||||||
|
'{authority}',
|
||||||
|
'{path}',
|
||||||
|
'{query}',
|
||||||
|
'{fragment}',
|
||||||
|
], [
|
||||||
|
$this->formatComponent($this->getScheme($flags), '', ':'),
|
||||||
|
$this->authority ? "//{$this->formatComponent($this->getAuthority($flags))}" : '',
|
||||||
|
$this->formatComponent($this->getPath($pathFlags)),
|
||||||
|
$this->formatComponent($this->getQuery(), '?'),
|
||||||
|
$this->formatComponent($this->getFragment(), '#'),
|
||||||
|
], $format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get compontsns as array; as parse_url() method
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function getComponents(int $flags = 0): array
|
||||||
|
{
|
||||||
|
return array_filter([
|
||||||
|
'scheme' => $this->getScheme($flags),
|
||||||
|
'host' => $this->getHost($flags),
|
||||||
|
'port' => $this->getPort($flags | self::REQUIRE_PORT),
|
||||||
|
'user' => $this->user,
|
||||||
|
'pass' => $this->pass,
|
||||||
|
'path' => $this->getPath($flags),
|
||||||
|
'query' => $this->getQuery($flags),
|
||||||
|
'fragment' => $this->getFragment($flags),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an instance with the specified compontents set.
|
||||||
|
* @param array<string, mixed> $components
|
||||||
|
* @param int<0, 256> $flags
|
||||||
|
* @return self A new instance with the specified components
|
||||||
|
*/
|
||||||
|
public function withComponents(array $components, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
foreach ($components as $component => $value) {
|
||||||
|
switch ($component) {
|
||||||
|
case 'port':
|
||||||
|
$clone->setPort($value, $flags);
|
||||||
|
break;
|
||||||
|
case 'scheme':
|
||||||
|
$clone->setScheme($value, $flags);
|
||||||
|
break;
|
||||||
|
case 'host':
|
||||||
|
$clone->setHost($value, $flags);
|
||||||
|
break;
|
||||||
|
case 'path':
|
||||||
|
$clone->setPath($value, $flags);
|
||||||
|
break;
|
||||||
|
case 'query':
|
||||||
|
$clone->setQuery($value, $flags);
|
||||||
|
break;
|
||||||
|
case 'fragment':
|
||||||
|
$clone->setFragment($value, $flags);
|
||||||
|
break;
|
||||||
|
case 'userInfo':
|
||||||
|
$clone->setUserInfo(...$value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException("Invalid URI component: '{$component}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all query items (if any) as associative array.
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return array<array-key, mixed> Query items
|
||||||
|
*/
|
||||||
|
public function getQueryItems(int $flags = 0): array
|
||||||
|
{
|
||||||
|
parse_str($this->getQuery(), $result);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return query item value for named query item, or null if not present.
|
||||||
|
* @param string $name Name of query item to retrieve
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return string|null|array<string, mixed> Query item value
|
||||||
|
*/
|
||||||
|
public function getQueryItem(string $name, int $flags = 0): array|string|null
|
||||||
|
{
|
||||||
|
parse_str($this->getQuery(), $result);
|
||||||
|
return $result[$name] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add query items as associative array that will be merged qith current items.
|
||||||
|
* @param array<string, mixed> $items Array of query items to add
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the added query items
|
||||||
|
*/
|
||||||
|
public function withQueryItems(array $items, int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = $this->clone($flags);
|
||||||
|
$clone->setQuery(http_build_query(
|
||||||
|
$this->queryMerge($this->getQueryItems($flags), $items),
|
||||||
|
'',
|
||||||
|
null,
|
||||||
|
PHP_QUERY_RFC3986
|
||||||
|
), $flags);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add query item value for named query item
|
||||||
|
* @param string $name Name of query item to add
|
||||||
|
* @param string|null|array<string, mixed> $value Value of query item to add
|
||||||
|
* @param int $flags Optional modifier flags
|
||||||
|
* @return self A new instance with the added query items
|
||||||
|
*/
|
||||||
|
public function withQueryItem(string $name, array|string|null $value, int $flags = 0): self
|
||||||
|
{
|
||||||
|
return $this->withQueryItems([$name => $value], $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Protected helper methods ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
protected function setPort(int|null $port, int $flags = 0): void
|
||||||
|
{
|
||||||
|
if ($port !== null && ($port < 0 || $port > 65535)) {
|
||||||
|
throw new InvalidArgumentException("Invalid port '{$port}'");
|
||||||
|
}
|
||||||
|
$this->port = $port;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setScheme(string $scheme, int $flags = 0): void
|
||||||
|
{
|
||||||
|
$pattern = '/^[a-z][a-z0-9-+.]*$/i';
|
||||||
|
if ($scheme !== '' && preg_match($pattern, $scheme) == 0) {
|
||||||
|
throw new InvalidArgumentException("Invalid scheme '{$scheme}': Should match {$pattern}");
|
||||||
|
}
|
||||||
|
$this->scheme = mb_strtolower($scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setHost(string $host, int $flags = 0): void
|
||||||
|
{
|
||||||
|
$this->authority = $this->authority || $host !== '';
|
||||||
|
if ($flags & self::IDNA) {
|
||||||
|
trigger_error("Flag IDNA is deprecated; use IDN_ENCODE instead", E_USER_DEPRECATED);
|
||||||
|
$host = $this->idnEncode($host);
|
||||||
|
}
|
||||||
|
if ($flags & self::IDN_ENCODE) {
|
||||||
|
$host = $this->idnEncode($host);
|
||||||
|
}
|
||||||
|
if ($flags & self::IDN_DECODE) {
|
||||||
|
$host = $this->idnDecode($host);
|
||||||
|
}
|
||||||
|
$this->host = mb_strtolower($host);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setPath(string $path, int $flags = 0): void
|
||||||
|
{
|
||||||
|
if ($flags & self::NORMALIZE_PATH) {
|
||||||
|
$path = $this->normalizePath($path);
|
||||||
|
}
|
||||||
|
if ($flags & self::ABSOLUTE_PATH && substr($path, 0, 1) !== '/') {
|
||||||
|
$path = "/{$path}";
|
||||||
|
}
|
||||||
|
$this->path = $this->uriDecode($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setQuery(string $query, int $flags = 0): void
|
||||||
|
{
|
||||||
|
$this->query = $this->uriDecode($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setFragment(string $fragment, int $flags = 0): void
|
||||||
|
{
|
||||||
|
$this->fragment = $this->uriDecode($fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setUser(string $user, int $flags = 0): void
|
||||||
|
{
|
||||||
|
$this->user = $this->uriDecode($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setPassword(string|null $pass, int $flags = 0): void
|
||||||
|
{
|
||||||
|
$this->pass = $pass === null ? null : $this->uriDecode($pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setUserInfo(string $user = '', string|null $pass = null, int $flags = 0): void
|
||||||
|
{
|
||||||
|
$this->setUser($user);
|
||||||
|
$this->setPassword($pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Private helper methods ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private function parse(string $uriString = ''): void
|
||||||
|
{
|
||||||
|
if ($uriString === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
preg_match(self::RE_MAIN, $uriString, $main);
|
||||||
|
$this->authority = !empty($main['authorityc']);
|
||||||
|
$this->setScheme($main['scheme'] ?? '');
|
||||||
|
$this->setPath($main['path'] ?? '');
|
||||||
|
$this->setQuery($main['query'] ?? '');
|
||||||
|
$this->setFragment($main['fragment'] ?? '');
|
||||||
|
if ($this->authority && !empty($main['authority'])) {
|
||||||
|
preg_match(self::RE_AUTH, $main['authority'], $auth);
|
||||||
|
if (empty($auth)) {
|
||||||
|
throw new InvalidArgumentException("Invalid 'authority'.");
|
||||||
|
}
|
||||||
|
if ($auth['host'] === '' && $auth['user'] !== '') {
|
||||||
|
throw new InvalidArgumentException("Invalid 'authority'.");
|
||||||
|
}
|
||||||
|
$this->setUser($auth['user'] ?? '');
|
||||||
|
$this->setPassword($auth['pass'] ?? null);
|
||||||
|
$this->setHost($auth['host'] ?? '');
|
||||||
|
$this->setPort(isset($auth['port']) ? (int)$auth['port'] : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function clone(int $flags = 0): self
|
||||||
|
{
|
||||||
|
$clone = clone $this;
|
||||||
|
if ($flags & self::REQUIRE_PORT) {
|
||||||
|
$clone->setPort($this->getPort(self::REQUIRE_PORT), $flags);
|
||||||
|
}
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function uriEncode(string $source, int $flags = 0, string $keep = ''): string
|
||||||
|
{
|
||||||
|
if ($flags & self::URI_DECODE) {
|
||||||
|
return $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
$unreserved = 'a-zA-Z0-9_\-\.~';
|
||||||
|
$subdelim = '!\$&\'\(\)\*\+,;=';
|
||||||
|
$char = '\pL';
|
||||||
|
$pct = '%(?![A-Fa-f0-9]{2}))';
|
||||||
|
|
||||||
|
$re = "/(?:[^%{$unreserved}{$subdelim}{$keep}]+|{$pct}/u";
|
||||||
|
|
||||||
|
if ($flags & self::URI_ENCODE) {
|
||||||
|
$re = "/(?:[^%{$unreserved}{$subdelim}{$keep}{$char}]+|{$pct}/u";
|
||||||
|
}
|
||||||
|
return preg_replace_callback($re, function ($matches) {
|
||||||
|
return rawurlencode($matches[0]);
|
||||||
|
}, $source) ?? $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function uriDecode(string $source): string
|
||||||
|
{
|
||||||
|
$re = "/(%[A-Fa-f0-9]{2})/u";
|
||||||
|
return preg_replace_callback($re, function ($matches) {
|
||||||
|
return rawurldecode($matches[0]);
|
||||||
|
}, $source) ?? $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatComponent(string|int|null $value, string $before = '', string $after = ''): string
|
||||||
|
{
|
||||||
|
$string = strval($value);
|
||||||
|
return $string === '' ? '' : "{$before}{$string}{$after}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizePath(string $path): string
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
preg_match_all('!([^/]*/|[^/]*$)!', $path, $items);
|
||||||
|
foreach ($items[0] as $item) {
|
||||||
|
switch ($item) {
|
||||||
|
case '':
|
||||||
|
case './':
|
||||||
|
case '.':
|
||||||
|
break; // just skip
|
||||||
|
case '/':
|
||||||
|
if (empty($result)) {
|
||||||
|
array_push($result, $item); // add
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '..':
|
||||||
|
case '../':
|
||||||
|
if (empty($result) || end($result) == '../') {
|
||||||
|
array_push($result, $item); // add
|
||||||
|
} else {
|
||||||
|
array_pop($result); // remove previous
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
array_push($result, $item); // add
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode('', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function idnEncode(string $value): string
|
||||||
|
{
|
||||||
|
if ($value === '' || !function_exists('idn_to_ascii')) {
|
||||||
|
return $value; // Can't convert, but don't cause exception
|
||||||
|
}
|
||||||
|
return idn_to_ascii($value, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46) ?: $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function idnDecode(string $value): string
|
||||||
|
{
|
||||||
|
if ($value === '' || !function_exists('idn_to_utf8')) {
|
||||||
|
return $value; // Can't convert, but don't cause exception
|
||||||
|
}
|
||||||
|
return idn_to_utf8($value, IDNA_NONTRANSITIONAL_TO_UNICODE, INTL_IDNA_VARIANT_UTS46) ?: $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $a
|
||||||
|
* @param array<string, mixed> $b
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function queryMerge(array $a, array $b): array
|
||||||
|
{
|
||||||
|
foreach ($b as $key => $value) {
|
||||||
|
if (is_int($key)) {
|
||||||
|
$a[] = $value;
|
||||||
|
} elseif (is_array($value)) {
|
||||||
|
$a[$key] = $this->queryMerge($a[$key] ?? [], $b[$key] ?? []);
|
||||||
|
} elseif (is_scalar($value)) {
|
||||||
|
$a[$key] = $this->uriDecode($b[$key]);
|
||||||
|
} else {
|
||||||
|
unset($a[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for Net\UriFactory class.
|
||||||
|
* @package Phrity > Net > Uri
|
||||||
|
* @see https://www.rfc-editor.org/rfc/rfc3986
|
||||||
|
* @see https://www.php-fig.org/psr/psr-17/#26-urifactoryinterface
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Net;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Psr\Http\Message\{
|
||||||
|
UriFactoryInterface,
|
||||||
|
UriInterface
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Net\UriFactory class.
|
||||||
|
*/
|
||||||
|
class UriFactory implements UriFactoryInterface
|
||||||
|
{
|
||||||
|
// ---------- PSR-7 methods ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new URI.
|
||||||
|
* @param string $uri The URI to parse.
|
||||||
|
* @throws InvalidArgumentException If the given URI cannot be parsed
|
||||||
|
*/
|
||||||
|
public function createUri(string $uri = ''): UriInterface
|
||||||
|
{
|
||||||
|
return new Uri($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- Extensions ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new URI from existing.
|
||||||
|
* @param UriInterface $uri A URI instance to create from.
|
||||||
|
* @throws InvalidArgumentException If the given URI cannot be parsed
|
||||||
|
*/
|
||||||
|
public function createUriFromInterface(UriInterface $uri): UriInterface
|
||||||
|
{
|
||||||
|
return new Uri($uri->__toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sören Jensen
|
||||||
|
|
||||||
|
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.
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/util-accessor",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Utility to handle access to a data set by using access paths. Similar to and partly compatible with xpath and json-pointer.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-accessor",
|
||||||
|
"keywords": ["accessor"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Test\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-transformer": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util;
|
||||||
|
|
||||||
|
use Phrity\Util\Transformer\TransformerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessor utility class.
|
||||||
|
*/
|
||||||
|
class Accessor
|
||||||
|
{
|
||||||
|
use AccessorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var non-empty-string $separator Separator
|
||||||
|
*/
|
||||||
|
protected string $separator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for this class.
|
||||||
|
* @param non-empty-string $separator Separator
|
||||||
|
* @param TransformerInterface|null $transformer Transformer
|
||||||
|
*/
|
||||||
|
public function __construct(string $separator = '/', TransformerInterface|null $transformer = null)
|
||||||
|
{
|
||||||
|
$this->separator = $separator;
|
||||||
|
$this->accessorTransformer = $transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get specified content from data set.
|
||||||
|
* @param mixed $data Data set to access
|
||||||
|
* @param string $path Path to access
|
||||||
|
* @param mixed $default Default value
|
||||||
|
* @param string|null $coerce Optional type coercion
|
||||||
|
* @return mixed Specified content of data set
|
||||||
|
*/
|
||||||
|
public function get(mixed $data, string $path, mixed $default = null, string|null $coerce = null): mixed
|
||||||
|
{
|
||||||
|
return $this->accessorGet($data, $this->accessorParsePath($path, $this->separator), $default, $coerce);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check specified content in data set.
|
||||||
|
* @param mixed $data Data set to access
|
||||||
|
* @param string $path Path to access
|
||||||
|
* @return bool If speciefied content is present
|
||||||
|
*/
|
||||||
|
public function has(mixed $data, string $path): bool
|
||||||
|
{
|
||||||
|
return $this->accessorHas($data, $this->accessorParsePath($path, $this->separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set specified content on data set.
|
||||||
|
* @param mixed $data Data set to modify
|
||||||
|
* @param string $path Path to access
|
||||||
|
* @param mixed $value Value to set
|
||||||
|
* @return mixed Modified data set
|
||||||
|
*/
|
||||||
|
public function set(mixed $data, string $path, mixed $value): mixed
|
||||||
|
{
|
||||||
|
return $this->accessorSet($data, $this->accessorParsePath($path, $this->separator), $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessor exception class.
|
||||||
|
*/
|
||||||
|
class AccessorException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util;
|
||||||
|
|
||||||
|
use Phrity\Util\Transformer\{
|
||||||
|
BasicTypeConverter,
|
||||||
|
TransformerInterface,
|
||||||
|
Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessor utility trait.
|
||||||
|
*/
|
||||||
|
trait AccessorTrait
|
||||||
|
{
|
||||||
|
private TransformerInterface|null $accessorTransformer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive worker function for get() operation.
|
||||||
|
* @param mixed $data Data set to access
|
||||||
|
* @param array<string> $path Path to access
|
||||||
|
* @param mixed $default Default value
|
||||||
|
* @param string|null $coerce Optional type coercion
|
||||||
|
* @return mixed Specified content of data set
|
||||||
|
*/
|
||||||
|
private function accessorGet(mixed $data, array $path, mixed $default, string|null $coerce = null): mixed
|
||||||
|
{
|
||||||
|
if (empty($path)) {
|
||||||
|
if ($coerce && $this->accessorGetTransformer()->canTransform($data, $coerce)) {
|
||||||
|
return $this->accessorGetTransformer()->transform($data, $coerce);
|
||||||
|
}
|
||||||
|
return $data; // Bottom case
|
||||||
|
}
|
||||||
|
$current = array_shift($path);
|
||||||
|
if ($this->accessorGetTransformer()->canTransform($data)) {
|
||||||
|
$data = $this->accessorGetTransformer()->transform($data);
|
||||||
|
}
|
||||||
|
if (is_array($data) && array_key_exists($current, $data)) {
|
||||||
|
return $this->accessorGet($data[$current], $path, $default, $coerce);
|
||||||
|
}
|
||||||
|
if (is_object($data) && property_exists($data, $current)) {
|
||||||
|
return $this->accessorGet($data->$current, $path, $default, $coerce);
|
||||||
|
}
|
||||||
|
return $default; // No match
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive worker function for has() operation.
|
||||||
|
* @param mixed $data Data set to access
|
||||||
|
* @param array<string> $path Path to access
|
||||||
|
* @return bool If speciefied content is present
|
||||||
|
*/
|
||||||
|
private function accessorHas(mixed $data, array $path): bool
|
||||||
|
{
|
||||||
|
if (empty($path)) {
|
||||||
|
return true; // Bottom case
|
||||||
|
}
|
||||||
|
$current = array_shift($path);
|
||||||
|
if ($this->accessorGetTransformer()->canTransform($data)) {
|
||||||
|
$data = $this->accessorGetTransformer()->transform($data);
|
||||||
|
}
|
||||||
|
if (is_array($data) && array_key_exists($current, $data)) {
|
||||||
|
return $this->accessorHas($data[$current], $path);
|
||||||
|
}
|
||||||
|
if (is_object($data) && property_exists($data, $current)) {
|
||||||
|
return $this->accessorHas($data->$current, $path);
|
||||||
|
}
|
||||||
|
return false; // No match
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive worker function for set() operation.
|
||||||
|
* @param mixed $data Data set to modify
|
||||||
|
* @param array<string> $path Path to access
|
||||||
|
* @param mixed $value Value to set
|
||||||
|
* @return mixed Modified data set
|
||||||
|
*/
|
||||||
|
private function accessorSet(mixed $data, array $path, mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (empty($path)) {
|
||||||
|
return $value; // Bottom case
|
||||||
|
}
|
||||||
|
$current = array_shift($path);
|
||||||
|
if ($this->accessorGetTransformer()->canTransform($data)) {
|
||||||
|
$data = $this->accessorGetTransformer()->transform($data);
|
||||||
|
}
|
||||||
|
if (is_object($data)) {
|
||||||
|
$data->$current = $this->accessorSet($data->$current ?? null, $path, $value);
|
||||||
|
}
|
||||||
|
if (is_array($data)) {
|
||||||
|
$data[$current] = $this->accessorSet($data[$current] ?? null, $path, $value);
|
||||||
|
}
|
||||||
|
if (is_null($data) || is_scalar($data)) {
|
||||||
|
$data = [];
|
||||||
|
$data[$current] = $this->accessorSet(null, $path, $value);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse string path into array segments.
|
||||||
|
* @param string $path Path to parse
|
||||||
|
* @param non-empty-string $separator Separator token
|
||||||
|
* @return array<string> Path segments as array
|
||||||
|
*/
|
||||||
|
private function accessorParsePath(string $path, string $separator): array
|
||||||
|
{
|
||||||
|
return array_filter(explode($separator, $path), function (string $item): bool {
|
||||||
|
return $item !== '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set Transformer to use, BasicTypeConverter used as default.
|
||||||
|
* @return TransformerInterface
|
||||||
|
*/
|
||||||
|
private function accessorGetTransformer(): TransformerInterface
|
||||||
|
{
|
||||||
|
if (!$this->accessorTransformer) {
|
||||||
|
$this->accessorTransformer = new BasicTypeConverter(); // Default type converter
|
||||||
|
}
|
||||||
|
return $this->accessorTransformer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
use Phrity\Util\Transformer\TransformerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataAccessor utility class.
|
||||||
|
*/
|
||||||
|
class DataAccessor implements JsonSerializable
|
||||||
|
{
|
||||||
|
use AccessorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var mixed $data Data set
|
||||||
|
*/
|
||||||
|
protected mixed $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var non-empty-string $separator Separator
|
||||||
|
*/
|
||||||
|
protected string $separator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for this class.
|
||||||
|
* @param mixed $data Data set to access
|
||||||
|
* @param non-empty-string $separator Separator
|
||||||
|
* @param TransformerInterface|null $transformer Transformer
|
||||||
|
*/
|
||||||
|
public function __construct(mixed $data, string $separator = '/', TransformerInterface|null $transformer = null)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
$this->separator = $separator;
|
||||||
|
$this->accessorTransformer = $transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get specified content from data set.
|
||||||
|
* @param string $path Path to access
|
||||||
|
* @param mixed $default Default value
|
||||||
|
* @param string|null $coerce Optional type coercion
|
||||||
|
* @return mixed Specified content of data set
|
||||||
|
*/
|
||||||
|
public function get(string $path, mixed $default = null, string|null $coerce = null): mixed
|
||||||
|
{
|
||||||
|
return $this->accessorGet($this->data, $this->accessorParsePath($path, $this->separator), $default, $coerce);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check specified content in data set.
|
||||||
|
* @param string $path Path to access
|
||||||
|
* @return bool If speciefied content is present
|
||||||
|
*/
|
||||||
|
public function has(string $path): bool
|
||||||
|
{
|
||||||
|
return $this->accessorHas($this->data, $this->accessorParsePath($path, $this->separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set specified content on data set.
|
||||||
|
* @param string $path Path to access
|
||||||
|
* @param mixed $value Value to set
|
||||||
|
* @return mixed Modified data set
|
||||||
|
*/
|
||||||
|
public function set(string $path, mixed $value): mixed
|
||||||
|
{
|
||||||
|
$this->data = $this->accessorSet($this->data, $this->accessorParsePath($path, $this->separator), $value);
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): mixed
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util;
|
||||||
|
|
||||||
|
use Phrity\Util\Transformer\TransformerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PathAccessor utility class.
|
||||||
|
*/
|
||||||
|
class PathAccessor
|
||||||
|
{
|
||||||
|
use AccessorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $path Path
|
||||||
|
*/
|
||||||
|
protected string $path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var non-empty-string $separator Separator
|
||||||
|
*/
|
||||||
|
protected string $separator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for this class.
|
||||||
|
* @param string $path Path
|
||||||
|
* @param non-empty-string $separator Separator
|
||||||
|
* @param TransformerInterface|null $transformer Transformer
|
||||||
|
*/
|
||||||
|
public function __construct(string $path, string $separator = '/', TransformerInterface|null $transformer = null)
|
||||||
|
{
|
||||||
|
$this->path = $path;
|
||||||
|
$this->separator = $separator;
|
||||||
|
$this->accessorTransformer = $transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get specified content from data set.
|
||||||
|
* @param mixed $data Data set to access
|
||||||
|
* @param mixed $default Default value
|
||||||
|
* @return mixed Specified content of data set
|
||||||
|
*/
|
||||||
|
public function get(mixed $data, mixed $default = null, string|null $coerce = null): mixed
|
||||||
|
{
|
||||||
|
return $this->accessorGet($data, $this->accessorParsePath($this->path, $this->separator), $default, $coerce);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check specified content in data set.
|
||||||
|
* @param mixed $data Data set to access
|
||||||
|
* @return bool If speciefied content is present
|
||||||
|
*/
|
||||||
|
public function has(mixed $data): bool
|
||||||
|
{
|
||||||
|
return $this->accessorHas($data, $this->accessorParsePath($this->path, $this->separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set specified content on data set.
|
||||||
|
* @param mixed $data Data set to modify
|
||||||
|
* @param mixed $value Value to set
|
||||||
|
* @return mixed Modified data set
|
||||||
|
*/
|
||||||
|
public function set(mixed $data, mixed $value): mixed
|
||||||
|
{
|
||||||
|
return $this->accessorSet($data, $this->accessorParsePath($this->path, $this->separator), $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sören Jensen
|
||||||
|
|
||||||
|
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.
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/util-errorhandler",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Inline error handler; catch and resolve errors for code block.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-errorhandler",
|
||||||
|
"keywords": ["error", "warning"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File for ErrorHandler utility class.
|
||||||
|
* @package Phrity > Util > ErrorHandler
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Phrity\Util;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use ErrorException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorHandler utility class.
|
||||||
|
* Allows catching and resolving errors inline.
|
||||||
|
*/
|
||||||
|
class ErrorHandler
|
||||||
|
{
|
||||||
|
/* ----------------- Public methods ---------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set error handler to run until removed.
|
||||||
|
* @param Closure|Throwable|null $handling
|
||||||
|
* - If null, handler will throw ErrorException
|
||||||
|
* - If Throwable $t, throw $t with ErrorException attached as previous
|
||||||
|
* - If callable, will invoke callback with ErrorException as argument
|
||||||
|
* @param int $levels Error levels to catch, all errors by default
|
||||||
|
* @return (callable(): mixed)|null Previously registered error handler, if any
|
||||||
|
*/
|
||||||
|
public function set(Closure|Throwable|null $handling = null, int $levels = E_ALL): callable|null
|
||||||
|
{
|
||||||
|
return set_error_handler($this->getHandler($handling), $levels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove error handler.
|
||||||
|
* @return bool True if removed
|
||||||
|
*/
|
||||||
|
public function restore(): bool
|
||||||
|
{
|
||||||
|
return restore_error_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run code with error handling, breaks on first encountered error.
|
||||||
|
* @param callable $callback The code to run
|
||||||
|
* @param Closure|Throwable|null $handling
|
||||||
|
* - If null, handler will throw ErrorException
|
||||||
|
* - If Throwable $t, throw $t with ErrorException attached as previous
|
||||||
|
* - If callable, will invoke callback with ErrorException as argument
|
||||||
|
* @param int $levels Error levels to catch, all errors by default
|
||||||
|
* @return mixed Return what $callback returns, or what $handling retuns on error
|
||||||
|
*/
|
||||||
|
public function with(callable $callback, Closure|Throwable|null $handling = null, int $levels = E_ALL): mixed
|
||||||
|
{
|
||||||
|
$error = null;
|
||||||
|
$result = null;
|
||||||
|
try {
|
||||||
|
$this->set(null, $levels);
|
||||||
|
$result = $callback();
|
||||||
|
} catch (ErrorException $e) {
|
||||||
|
$error = $this->handle($handling, $e);
|
||||||
|
} finally {
|
||||||
|
$this->restore();
|
||||||
|
}
|
||||||
|
return $error ?? $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run code with error handling, comletes code before handling errors
|
||||||
|
* @param callable $callback The code to run
|
||||||
|
* @param Closure|Throwable|null $handling
|
||||||
|
* - If null, handler will throw ErrorException
|
||||||
|
* - If Throwable $t, throw $t with ErrorException attached as previous
|
||||||
|
* - If callable, will invoke callback with ErrorException as argument
|
||||||
|
* @param int $levels Error levels to catch, all errors by default
|
||||||
|
* @return mixed Return what $callback returns, or what $handling retuns on error
|
||||||
|
*/
|
||||||
|
public function withAll(callable $callback, Closure|Throwable|null $handling = null, int $levels = E_ALL): mixed
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
|
$this->set(function (ErrorException $e) use (&$errors) {
|
||||||
|
$errors[] = $e;
|
||||||
|
}, $levels);
|
||||||
|
$result = $callback();
|
||||||
|
$this->restore();
|
||||||
|
$error = empty($errors) ? null : $this->handle($handling, $errors, $result);
|
||||||
|
return $error ?? $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ----------------- Private helpers --------------------------------------------- */
|
||||||
|
|
||||||
|
// Get handler function
|
||||||
|
private function getHandler(Closure|Throwable|null $handling): Closure
|
||||||
|
{
|
||||||
|
return function ($severity, $message, $file, $line) use ($handling) {
|
||||||
|
$error = new ErrorException($message, 0, $severity, $file, $line);
|
||||||
|
$this->handle($handling, $error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle error according to $handlig type
|
||||||
|
* @param Closure|Throwable|null $handling
|
||||||
|
* @param ErrorException|non-empty-array<ErrorException> $error
|
||||||
|
* @mixed $result
|
||||||
|
* @return mixed
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
private function handle(Closure|Throwable|null $handling, ErrorException|array $error, mixed $result = null): mixed
|
||||||
|
{
|
||||||
|
if (is_callable($handling)) {
|
||||||
|
return $handling($error, $result);
|
||||||
|
}
|
||||||
|
if (is_array($error)) {
|
||||||
|
$error = array_shift($error);
|
||||||
|
}
|
||||||
|
if ($handling instanceof Throwable) {
|
||||||
|
try {
|
||||||
|
/* @phpstan-ignore finally.exitPoint */
|
||||||
|
throw $error;
|
||||||
|
} finally {
|
||||||
|
/* @phpstan-ignore finally.exitPoint */
|
||||||
|
throw $handling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw $error;
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sören Jensen
|
||||||
|
|
||||||
|
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.
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/util-interpolator",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Interpolator class and trait.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-interpolator",
|
||||||
|
"keywords": ["interpolator", "interpolate"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Interpolator\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Interpolator\\Test\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"phrity/util-accessor": "^1.3",
|
||||||
|
"phrity/util-transformer": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Interpolator;
|
||||||
|
|
||||||
|
use Phrity\Util\Transformer\TransformerInterface;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
class Interpolator
|
||||||
|
{
|
||||||
|
use InterpolatorTrait {
|
||||||
|
interpolate as protected traitInterpolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param non-empty-string $separator
|
||||||
|
* @param TransformerInterface|null $transformer
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private string $separator = '.',
|
||||||
|
private TransformerInterface|null $transformer = null,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|Stringable $source
|
||||||
|
* @param array<array-key, mixed>|object $replacers
|
||||||
|
*/
|
||||||
|
public function interpolate(
|
||||||
|
string|Stringable $source,
|
||||||
|
array|object $replacers = [],
|
||||||
|
): string {
|
||||||
|
return $this->traitInterpolate($source, $replacers, $this->separator, $this->transformer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Interpolator;
|
||||||
|
|
||||||
|
use Phrity\Util\DataAccessor;
|
||||||
|
use Phrity\Util\Transformer\{
|
||||||
|
BasicTypeConverter,
|
||||||
|
DateTimeConverter,
|
||||||
|
EnumConverter,
|
||||||
|
FirstMatchResolver,
|
||||||
|
ReadableConverter,
|
||||||
|
StringableConverter,
|
||||||
|
ThrowableConverter,
|
||||||
|
TransformerInterface,
|
||||||
|
Type,
|
||||||
|
};
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
trait InterpolatorTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string|Stringable $source
|
||||||
|
* @param array<array-key, mixed>|object $replacers
|
||||||
|
* @param non-empty-string $separator
|
||||||
|
* @param TransformerInterface|null $transformer
|
||||||
|
*/
|
||||||
|
public function interpolate(
|
||||||
|
string|Stringable $source,
|
||||||
|
array|object $replacers = [],
|
||||||
|
string $separator = '.',
|
||||||
|
TransformerInterface|null $transformer = null,
|
||||||
|
): string {
|
||||||
|
$transformer = $transformer ?? new FirstMatchResolver([
|
||||||
|
new DateTimeConverter(),
|
||||||
|
new EnumConverter(),
|
||||||
|
new ReadableConverter(),
|
||||||
|
new ThrowableConverter(),
|
||||||
|
new StringableConverter(),
|
||||||
|
new BasicTypeConverter(),
|
||||||
|
]);
|
||||||
|
$accessor = new DataAccessor($replacers, $separator, $transformer);
|
||||||
|
$result = preg_replace_callback('/{([^{}]{1,})}/', function (array $matches) use ($accessor): string {
|
||||||
|
return $accessor->get($matches[1], $matches[0], Type::STRING);
|
||||||
|
}, $source);
|
||||||
|
return $result ?? $source;
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sören Jensen
|
||||||
|
|
||||||
|
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.
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/util-transformer",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Type transformers, normalizers and resolvers.",
|
||||||
|
"homepage": "https://phrity.sirn.se/util-transformer",
|
||||||
|
"keywords": ["type", "transformer", "normalizer", "resolver"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Transformer\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phrity\\Util\\Transformer\\Test\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5 || ^4.0",
|
||||||
|
"symfony/property-access": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/serializer": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/translation-contracts": "^3.0",
|
||||||
|
"symfony/uid": "^6.0 || ^7.0 || ^8.0"
|
||||||
|
},
|
||||||
|
"suggests": {
|
||||||
|
"symfony/property-access": "Use Symfony normalizer as transformer",
|
||||||
|
"symfony/serializer": "Use Symfony normalizer as transformer"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
class BasicTypeConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
/** @var array<string|null> $supportedTypes */
|
||||||
|
private static array $supportedTypes = [
|
||||||
|
null,
|
||||||
|
Type::ARRAY,
|
||||||
|
Type::BOOLEAN,
|
||||||
|
Type::INTEGER,
|
||||||
|
Type::NULL,
|
||||||
|
Type::NUMBER,
|
||||||
|
Type::OBJECT,
|
||||||
|
Type::STRING,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var array<string, string> $typeMap */
|
||||||
|
private array $typeMap;
|
||||||
|
|
||||||
|
/** @param array<string, string> $typeMap */
|
||||||
|
public function __construct(array $typeMap = [])
|
||||||
|
{
|
||||||
|
$this->typeMap = $typeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
return in_array($type, self::$supportedTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
if (!in_array(gettype($subject), self::$supportedTypes)) {
|
||||||
|
$subject = get_debug_type($subject); // Can only extract type name
|
||||||
|
}
|
||||||
|
$subjectType = gettype($subject);
|
||||||
|
$targetType = $type ?? $this->typeMap[$subjectType] ?? $subjectType;
|
||||||
|
|
||||||
|
if (!$this->canTransform($subject, $targetType)) {
|
||||||
|
throw new TransformerException("Converting '{$subjectType}' to '{$targetType}' is not supported.");
|
||||||
|
}
|
||||||
|
/** @var Type::ARRAY|Type::BOOLEAN|Type::INTEGER|Type::NULL|Type::NUMBER|Type::OBJECT|Type::STRING $targetType */
|
||||||
|
return match ($targetType) {
|
||||||
|
Type::ARRAY => (array)match ($subjectType) {
|
||||||
|
Type::OBJECT => get_object_vars($subject),
|
||||||
|
default => $subject,
|
||||||
|
},
|
||||||
|
Type::BOOLEAN => (bool)$subject,
|
||||||
|
Type::INTEGER => (int)match ($subjectType) {
|
||||||
|
Type::OBJECT => (bool)$subject,
|
||||||
|
default => $subject,
|
||||||
|
},
|
||||||
|
Type::NULL => null,
|
||||||
|
Type::NUMBER => (float)match ($subjectType) {
|
||||||
|
Type::OBJECT => (bool)$subject,
|
||||||
|
default => $subject,
|
||||||
|
},
|
||||||
|
Type::OBJECT => (object)match ($subjectType) {
|
||||||
|
Type::OBJECT => get_object_vars($subject),
|
||||||
|
default => $subject,
|
||||||
|
},
|
||||||
|
Type::STRING => (string)match ($subjectType) {
|
||||||
|
Type::ARRAY, Type::OBJECT => get_debug_type($subject),
|
||||||
|
default => $subject,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class ChainedResolver implements TransformerInterface
|
||||||
|
{
|
||||||
|
/** @var array<TransformerInterface> $transformers */
|
||||||
|
private array $transformers;
|
||||||
|
private string|null $default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<TransformerInterface> $transformers
|
||||||
|
*/
|
||||||
|
public function __construct(array $transformers, string|null $default = null)
|
||||||
|
{
|
||||||
|
foreach ($transformers as $transformer) {
|
||||||
|
if (!$transformer instanceof TransformerInterface) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
"'%s' is not implementing %s",
|
||||||
|
get_debug_type($transformer),
|
||||||
|
TransformerInterface::class
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->transformers = $transformers;
|
||||||
|
$this->default = $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
foreach ($this->transformers as $transformer) {
|
||||||
|
if ($transformer->canTransform($subject, $type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
foreach ($this->transformers as $transformer) {
|
||||||
|
if ($transformer->canTransform($subject, $type)) {
|
||||||
|
$subject = $transformer->transform($subject, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$subjectType = gettype($subject);
|
||||||
|
if ($type !== null && $type !== $subjectType) {
|
||||||
|
throw new TransformerException("Could not find transformer for '{$subjectType}'.");
|
||||||
|
}
|
||||||
|
return $subject;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DatePeriod;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
class DateTimeConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $dateTimeFormat = 'c',
|
||||||
|
private string $dateIntervalFormat = '%d',
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
if (!in_array($type, [Type::STRING, null])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $subject instanceof DateInterval
|
||||||
|
|| $subject instanceof DatePeriod
|
||||||
|
|| $subject instanceof DateTimeInterface
|
||||||
|
|| $subject instanceof DateTimeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
throw new TransformerException("Converting '{$subjectType}' is not supported.");
|
||||||
|
}
|
||||||
|
if ($subject instanceof DateInterval) {
|
||||||
|
return $subject->format($this->dateIntervalFormat);
|
||||||
|
}
|
||||||
|
if ($subject instanceof DatePeriod) {
|
||||||
|
return sprintf(
|
||||||
|
'%s (%s)',
|
||||||
|
$subject->getStartDate()->format($this->dateTimeFormat),
|
||||||
|
$subject->getDateInterval()->format($this->dateIntervalFormat)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($subject instanceof DateTimeInterface) {
|
||||||
|
return $subject->format($this->dateTimeFormat);
|
||||||
|
}
|
||||||
|
return $subject->getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
|
class EnumConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
private string|null $default;
|
||||||
|
|
||||||
|
public function __construct(bool $perDefault = false)
|
||||||
|
{
|
||||||
|
$this->default = $perDefault ? Type::STRING : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
return $subject instanceof UnitEnum && $type == Type::STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
throw new TransformerException("Enum string for '{$subjectType}' is not supported.");
|
||||||
|
}
|
||||||
|
return $subject->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class FirstMatchResolver implements TransformerInterface
|
||||||
|
{
|
||||||
|
/** @var array<TransformerInterface> $transformers */
|
||||||
|
private array $transformers;
|
||||||
|
private string|null $default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<TransformerInterface> $transformers
|
||||||
|
* @param string|null $default
|
||||||
|
*/
|
||||||
|
public function __construct(array $transformers, string|null $default = null)
|
||||||
|
{
|
||||||
|
foreach ($transformers as $transformer) {
|
||||||
|
if (!$transformer instanceof TransformerInterface) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
"'%s' is not implementing %s",
|
||||||
|
get_debug_type($transformer),
|
||||||
|
TransformerInterface::class
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->transformers = $transformers;
|
||||||
|
$this->default = $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
foreach ($this->transformers as $transformer) {
|
||||||
|
if ($transformer->canTransform($subject, $type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
foreach ($this->transformers as $transformer) {
|
||||||
|
if ($transformer->canTransform($subject, $type)) {
|
||||||
|
return $transformer->transform($subject, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
throw new TransformerException("Could not find transformer for '{$subjectType}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use JsonException;
|
||||||
|
|
||||||
|
class FlattenDecoder implements TransformerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $separator,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
return in_array($type, [null, Type::ARRAY]) && in_array(gettype($subject), [Type::ARRAY, Type::OBJECT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
if (empty($this->separator)) {
|
||||||
|
return $subject;
|
||||||
|
}
|
||||||
|
$re = "|^([{$this->separator}]*[^{$this->separator}]+){$this->separator}(.+)|";
|
||||||
|
$coll = [];
|
||||||
|
foreach ($subject as $key => $value) {
|
||||||
|
if ($key == $this->separator) {
|
||||||
|
$coll[$this->separator] = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
preg_match($re, $key, $res);
|
||||||
|
$keyf = $res[1] ?? $key;
|
||||||
|
$keyl = $res[2] ?? $this->separator;
|
||||||
|
$coll[$keyf][$keyl] = $value;
|
||||||
|
}
|
||||||
|
foreach ($coll as $key => $sub) {
|
||||||
|
if (!is_array($sub)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$coll[$key] = $this->transform($sub);
|
||||||
|
}
|
||||||
|
if (count($coll) === 1 && isset($coll[$this->separator])) {
|
||||||
|
return $coll[$this->separator];
|
||||||
|
}
|
||||||
|
return $coll;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use JsonException;
|
||||||
|
|
||||||
|
class JsonDecoder implements TransformerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private bool $associative = false,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
if (gettype($subject) != Type::STRING) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return in_array($type, [null, gettype($this->decode($subject))]);
|
||||||
|
} catch (JsonException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TransformerException
|
||||||
|
*/
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
throw new TransformerException("JSON decoding for '{$subject}' is not supported.");
|
||||||
|
}
|
||||||
|
return $this->decode($subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws JsonException
|
||||||
|
*/
|
||||||
|
private function decode(string $subject): mixed
|
||||||
|
{
|
||||||
|
return json_decode($subject, associative: $this->associative, flags: JSON_THROW_ON_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
class JsonSerializableConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
if (!$subject instanceof JsonSerializable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return in_array($type, [null, gettype($subject->jsonSerialize())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
throw new TransformerException("JSON serialize for '{$subjectType}' is not supported.");
|
||||||
|
}
|
||||||
|
return $subject->jsonSerialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
class ReadableConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
private string|null $default;
|
||||||
|
|
||||||
|
public function __construct(bool $perDefault = false)
|
||||||
|
{
|
||||||
|
$this->default = $perDefault ? Type::STRING : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
return in_array(gettype($subject), [Type::BOOLEAN, Type::NULL]) && $type == Type::STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = gettype($subject);
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
throw new TransformerException("Creating readable for '{$subjectType}' is not supported.");
|
||||||
|
}
|
||||||
|
/** @var Type::BOOLEAN|Type::NULL $subjectType */
|
||||||
|
return match ($subjectType) {
|
||||||
|
Type::BOOLEAN => $subject ? 'true' : 'false',
|
||||||
|
Type::NULL => 'null',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
class RecursionResolver implements TransformerInterface
|
||||||
|
{
|
||||||
|
private TransformerInterface $transformer;
|
||||||
|
|
||||||
|
public function __construct(TransformerInterface $transformer)
|
||||||
|
{
|
||||||
|
$this->transformer = $transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
return $this->transformer->canTransform($subject, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$transformed = $this->transformer->transform($subject, $type);
|
||||||
|
if (is_array($transformed)) {
|
||||||
|
foreach ($transformed as $key => $content) {
|
||||||
|
if (is_array($content) || is_object($content)) {
|
||||||
|
$transformed[$key] = $this->transform($content, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_object($transformed)) {
|
||||||
|
foreach ((array)$transformed as $key => $content) {
|
||||||
|
if (is_array($content) || is_object($content)) {
|
||||||
|
$transformed->$key = $this->transform($content, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $transformed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
class ReversedReadableConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
$subjectType = gettype($subject);
|
||||||
|
|
||||||
|
if ($subjectType != Type::STRING || !in_array($type, [Type::BOOLEAN, Type::NULL, null])) {
|
||||||
|
return false; // Must be string
|
||||||
|
}
|
||||||
|
return (in_array(strtolower($subject), ['true', 'false', '1', '0', 'null', '']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = gettype($subject);
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
throw new TransformerException("Creating reversed readable for '{$subjectType}' is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var "true"|"false"|"null"|"1"|"0"|"" $subject */
|
||||||
|
$subject = strtolower($subject);
|
||||||
|
return match ($subject) {
|
||||||
|
'true', '1' => true,
|
||||||
|
'false', '0' => false,
|
||||||
|
'null' => null,
|
||||||
|
'' => $type == Type::NULL ? null : false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
class StringResolver implements TransformerInterface
|
||||||
|
{
|
||||||
|
private BasicTypeConverter $converter;
|
||||||
|
private TransformerInterface $transformer;
|
||||||
|
|
||||||
|
public function __construct(TransformerInterface|null $transformer = null)
|
||||||
|
{
|
||||||
|
$this->converter = new BasicTypeConverter();
|
||||||
|
$this->transformer = $transformer ?? $this->converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
if ($this->transformer->canTransform($subject, $type)) {
|
||||||
|
// Convert using configured transformes and type
|
||||||
|
$subject = $this->transformer->transform($subject, $type);
|
||||||
|
}
|
||||||
|
if (is_string($subject)) {
|
||||||
|
// If we get string, done here
|
||||||
|
return $subjectType == Type::STRING ? $this->string($subject) : $subject;
|
||||||
|
}
|
||||||
|
if (is_object($subject)) {
|
||||||
|
// Force object result to array using basic type converter
|
||||||
|
$subject = $this->converter->transform($subject, Type::ARRAY);
|
||||||
|
}
|
||||||
|
if (is_array($subject)) {
|
||||||
|
// Descend into recursive worker
|
||||||
|
return $this->wrap($subject, $type, array_is_list($subject));
|
||||||
|
}
|
||||||
|
// Force non-string scalars to string
|
||||||
|
return $this->converter->transform($subject, Type::STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<array-key, mixed> $items
|
||||||
|
*/
|
||||||
|
private function wrap(array $items, string|null $type, bool $isList): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s%s%s',
|
||||||
|
$isList ? '[' : '{',
|
||||||
|
implode(', ', $this->map($items, $type, $isList)),
|
||||||
|
$isList ? ']' : '}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<array-key, mixed> $items
|
||||||
|
* @return array<array-key, mixed>
|
||||||
|
*/
|
||||||
|
private function map(array $items, string|null $type, bool $isList): array
|
||||||
|
{
|
||||||
|
return array_map(function (mixed $value, mixed $key) use ($type, $isList): mixed {
|
||||||
|
return $this->index($this->transform($value, $type), $isList ? null : $key);
|
||||||
|
}, $items, array_keys($items));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function index(mixed $value, string|int|null $key = null): string
|
||||||
|
{
|
||||||
|
return is_null($key) ? $value : "{$key}: {$value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function string(mixed $value): string
|
||||||
|
{
|
||||||
|
return is_string($value) ? "\"{$value}\"" : $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
class StringableConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
private string|null $default;
|
||||||
|
|
||||||
|
public function __construct(bool $perDefault = false)
|
||||||
|
{
|
||||||
|
$this->default = $perDefault ? Type::STRING : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
return $subject instanceof Stringable && $type == Type::STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
throw new TransformerException("Creating stringable for '{$subjectType}' is not supported.");
|
||||||
|
}
|
||||||
|
return $subject->__toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer\Symfony;
|
||||||
|
|
||||||
|
use Phrity\Util\Transformer\{
|
||||||
|
TransformerException,
|
||||||
|
TransformerInterface,
|
||||||
|
Type,
|
||||||
|
};
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
class NormalizerWrapper implements TransformerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private NormalizerInterface $normalizer,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
if (!$this->normalizer->supportsNormalization($subject)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return in_array($type, [null, gettype($this->normalizer->normalize($subject))]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
if (!$this->canTransform($subject, $type)) {
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
$class = get_debug_type($this->normalizer);
|
||||||
|
throw new TransformerException("Normalizer {$class} to not support '{$subjectType}'.");
|
||||||
|
}
|
||||||
|
return $this->normalizer->normalize($subject);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class ThrowableConverter implements TransformerInterface
|
||||||
|
{
|
||||||
|
/** @var array<string|null> $supportedTypes */
|
||||||
|
private static array $supportedTypes = [
|
||||||
|
Type::ARRAY,
|
||||||
|
Type::OBJECT,
|
||||||
|
Type::STRING,
|
||||||
|
];
|
||||||
|
|
||||||
|
private string $default;
|
||||||
|
/** @var array<string> $parts */
|
||||||
|
private array $parts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Type::ARRAY|Type::OBJECT|Type::STRING $default
|
||||||
|
* @param array<string> $parts
|
||||||
|
*/
|
||||||
|
public function __construct(string $default = Type::OBJECT, array $parts = ['type', 'message', 'code'])
|
||||||
|
{
|
||||||
|
if (!in_array($default, self::$supportedTypes)) {
|
||||||
|
throw new InvalidArgumentException("Invalid '{$default}' provided");
|
||||||
|
}
|
||||||
|
$this->default = $default;
|
||||||
|
$this->parts = $parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool
|
||||||
|
{
|
||||||
|
$type ??= $this->default;
|
||||||
|
return $subject instanceof Throwable && in_array($type, self::$supportedTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed
|
||||||
|
{
|
||||||
|
$subjectType = get_debug_type($subject);
|
||||||
|
$targetType = $type ?? $this->default;
|
||||||
|
|
||||||
|
if (!$this->canTransform($subject, $targetType)) {
|
||||||
|
throw new TransformerException("Throwable conversion for '{$subjectType}' is not supported.");
|
||||||
|
}
|
||||||
|
/** @var Type::ARRAY|Type::OBJECT|Type::STRING $targetType */
|
||||||
|
return match ($targetType) {
|
||||||
|
Type::ARRAY => $this->createData($subject),
|
||||||
|
Type::OBJECT => (object)$this->createData($subject),
|
||||||
|
Type::STRING => $subject->getMessage(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Throwable $subject
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function createData(Throwable $subject): array
|
||||||
|
{
|
||||||
|
$data = [];
|
||||||
|
foreach ($this->parts as $part) {
|
||||||
|
$data[$part] = match ($part) {
|
||||||
|
'type' => $subject::class,
|
||||||
|
'message' => $subject->getMessage(),
|
||||||
|
'code' => $subject->getCode(),
|
||||||
|
'file' => $subject->getFile(),
|
||||||
|
'line' => $subject->getLine(),
|
||||||
|
'trace' => $subject->getTrace(),
|
||||||
|
'previous' => $subject->getPrevious(),
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
use LogicException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformer exception class.
|
||||||
|
*/
|
||||||
|
class TransformerException extends LogicException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
interface TransformerInterface
|
||||||
|
{
|
||||||
|
public function canTransform(mixed $subject, string|null $type = null): bool;
|
||||||
|
public function transform(mixed $subject, string|null $type = null): mixed;
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phrity\Util\Transformer;
|
||||||
|
|
||||||
|
interface Type
|
||||||
|
{
|
||||||
|
public const ARRAY = 'array';
|
||||||
|
public const BOOLEAN = 'boolean';
|
||||||
|
public const INTEGER = 'integer';
|
||||||
|
public const NULL = 'NULL';
|
||||||
|
public const NUMBER = 'double';
|
||||||
|
public const OBJECT = 'object';
|
||||||
|
public const STRING = 'string';
|
||||||
|
}
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Websocket: License
|
||||||
|
|
||||||
|
Websocket PHP is free software released under the following license:
|
||||||
|
|
||||||
|
[ISC License](http://en.wikipedia.org/wiki/ISC_license)
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without
|
||||||
|
fee is hereby granted, provided that the above copyright notice and this permission notice appear
|
||||||
|
in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
||||||
|
SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
THIS SOFTWARE.
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "phrity/websocket",
|
||||||
|
"type": "library",
|
||||||
|
"description": "WebSocket client and server",
|
||||||
|
"homepage": "https://phrity.sirn.se/websocket",
|
||||||
|
"keywords": ["websocket", "client", "server"],
|
||||||
|
"license": "ISC",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fredrik Liljegren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sören Jensen",
|
||||||
|
"email": "sirn@sirn.se",
|
||||||
|
"homepage": "https://phrity.sirn.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"WebSocket\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"WebSocket\\Test\\": "tests/mock/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"nyholm/psr7": "^1.4",
|
||||||
|
"phrity/http": "^1.1",
|
||||||
|
"phrity/net-uri": "^2.1",
|
||||||
|
"phrity/net-stream": "^2.4",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0",
|
||||||
|
"psr/log": "^1.0 || ^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phrity/logger-console": "^1.0",
|
||||||
|
"phrity/net-mock": "^2.4",
|
||||||
|
"phrity/util-errorhandler": "^1.1",
|
||||||
|
"robiningelbrecht/phpunit-coverage-tools": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^4.0"
|
||||||
|
},
|
||||||
|
"suggests": {
|
||||||
|
"ext-zlib": "Required for per-message deflate compression"
|
||||||
|
}
|
||||||
|
}
|
||||||
+764
@@ -0,0 +1,764 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2014-2026 Textalk and contributors.
|
||||||
|
* This file is part of Websocket PHP and is free software under the ISC License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WebSocket;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Phrity\Http\HttpFactory;
|
||||||
|
use Phrity\Net\{
|
||||||
|
Context,
|
||||||
|
StreamCollection,
|
||||||
|
StreamFactory,
|
||||||
|
Uri
|
||||||
|
};
|
||||||
|
use Psr\Http\Message\{
|
||||||
|
RequestInterface,
|
||||||
|
ResponseInterface,
|
||||||
|
UriInterface,
|
||||||
|
};
|
||||||
|
use Psr\Log\{
|
||||||
|
LoggerAwareInterface,
|
||||||
|
LoggerInterface,
|
||||||
|
};
|
||||||
|
use Stringable;
|
||||||
|
use Throwable;
|
||||||
|
use WebSocket\Exception\{
|
||||||
|
BadUriException,
|
||||||
|
ClientException,
|
||||||
|
CloseException,
|
||||||
|
ConnectionLevelInterface,
|
||||||
|
ExceptionInterface,
|
||||||
|
HandshakeException,
|
||||||
|
MessageLevelInterface,
|
||||||
|
ReconnectException,
|
||||||
|
};
|
||||||
|
use WebSocket\Http\DefaultHttpFactory;
|
||||||
|
use WebSocket\Message\Message;
|
||||||
|
use WebSocket\Middleware\MiddlewareInterface;
|
||||||
|
use WebSocket\Runtime\{
|
||||||
|
IdentityInterface,
|
||||||
|
Runner,
|
||||||
|
};
|
||||||
|
use WebSocket\Trait\{
|
||||||
|
ConfigurationTrait,
|
||||||
|
ListenerTrait,
|
||||||
|
SendMethodsTrait,
|
||||||
|
StringableTrait
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket\Client class.
|
||||||
|
* Entry class for WebSocket client.
|
||||||
|
*/
|
||||||
|
class Client implements IdentityInterface, LoggerAwareInterface, Stringable
|
||||||
|
{
|
||||||
|
use ConfigurationTrait;
|
||||||
|
/** @use ListenerTrait<Client> */
|
||||||
|
use ListenerTrait;
|
||||||
|
use SendMethodsTrait;
|
||||||
|
use StringableTrait;
|
||||||
|
|
||||||
|
private const SCOPE = 'client';
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
/** @var array<string, string> $headers */
|
||||||
|
private array $headers = [];
|
||||||
|
|
||||||
|
// Internal resources
|
||||||
|
private StreamFactory $streamFactory;
|
||||||
|
private Uri $socketUri;
|
||||||
|
private Connection|null $connection = null;
|
||||||
|
/** @var array<MiddlewareInterface> $middlewares */
|
||||||
|
private array $middlewares = [];
|
||||||
|
private Runner $runner;
|
||||||
|
private bool $running = false;
|
||||||
|
private HttpFactory $httpFactory;
|
||||||
|
/** @var non-empty-string $identity */
|
||||||
|
private string $identity = 'client/<unconnected>';
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Magic methods ------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param UriInterface|string $uri A ws/wss-URI
|
||||||
|
* @param Configuration|null $configuration
|
||||||
|
* @param StreamFactory|null $streamFactory
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
UriInterface|string $uri,
|
||||||
|
Configuration|null $configuration = null,
|
||||||
|
StreamFactory|null $streamFactory = null,
|
||||||
|
) {
|
||||||
|
$this->socketUri = $this->parseUri($uri);
|
||||||
|
$this->streamFactory = $streamFactory ?? new StreamFactory();
|
||||||
|
$this->httpFactory = new DefaultHttpFactory();
|
||||||
|
$this->identity = "client/{$this->socketUri->getHost()}";
|
||||||
|
$this->initConfiguration($configuration);
|
||||||
|
$this->runner = new Runner($this->streamFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get string representation of instance.
|
||||||
|
* @return string String representation
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->stringable('%s', $this->connection ? $this->socketUri->__toString() : 'closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Configuration ------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
public function getIdentity(): string
|
||||||
|
{
|
||||||
|
return $this->identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set stream factory to use.
|
||||||
|
* @param StreamFactory $streamFactory
|
||||||
|
* @return self
|
||||||
|
* @depracated Remove in v4
|
||||||
|
*/
|
||||||
|
public function setStreamFactory(StreamFactory $streamFactory): self
|
||||||
|
{
|
||||||
|
trigger_error('Client.setStreamFactory is deprecated and will be removed in v4.', E_USER_DEPRECATED);
|
||||||
|
$this->streamFactory = $streamFactory;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set HTTP factory to use.
|
||||||
|
* @param HttpFactory $httpFactory
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setHttpFactory(HttpFactory $httpFactory): self
|
||||||
|
{
|
||||||
|
$this->httpFactory = $httpFactory;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set logger.
|
||||||
|
* @param LoggerInterface $logger Logger implementation
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
*/
|
||||||
|
public function setLogger(LoggerInterface $logger): void
|
||||||
|
{
|
||||||
|
$this->configuration->setLogger($logger);
|
||||||
|
if ($this->connection) {
|
||||||
|
$this->connection->setLogger($logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set timeout.
|
||||||
|
* @param int<0, max>|float $timeout Timeout in seconds
|
||||||
|
* @return self
|
||||||
|
* @throws InvalidArgumentException If invalid timeout provided
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
*/
|
||||||
|
public function setTimeout(int|float $timeout): self
|
||||||
|
{
|
||||||
|
$this->configuration->setTimeout($timeout);
|
||||||
|
if ($this->connection) {
|
||||||
|
$this->connection->setTimeout($timeout);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timeout.
|
||||||
|
* @return int<0, max>|float Timeout in seconds
|
||||||
|
* @deprecated Will be removed in future version, get from Configuration instead
|
||||||
|
*/
|
||||||
|
public function getTimeout(): int|float
|
||||||
|
{
|
||||||
|
return $this->configuration->getTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set frame size.
|
||||||
|
* @param int<1, max> $frameSize Max frame payload size in bytes
|
||||||
|
* @return self
|
||||||
|
* @throws InvalidArgumentException If invalid frameSize provided
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
*/
|
||||||
|
public function setFrameSize(int $frameSize): self
|
||||||
|
{
|
||||||
|
$this->configuration->setFrameSize($frameSize);
|
||||||
|
if ($this->connection) {
|
||||||
|
$this->connection->setFrameSize($frameSize);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get frame size.
|
||||||
|
* @return int Frame size in bytes
|
||||||
|
* @deprecated Will be removed in future version, get from Configuration instead
|
||||||
|
*/
|
||||||
|
public function getFrameSize(): int
|
||||||
|
{
|
||||||
|
return $this->configuration->getFrameSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set connection persistence.
|
||||||
|
* @param bool $persistent True for persistent connection.
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setPersistent(bool $persistent): self
|
||||||
|
{
|
||||||
|
$this->configuration->setPersistent($persistent);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set stream context.
|
||||||
|
* @param Context|array<string, mixed> $context Context or options as array
|
||||||
|
* @see https://www.php.net/manual/en/context.php
|
||||||
|
* @return self
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
*/
|
||||||
|
public function setContext(Context|array $context): self
|
||||||
|
{
|
||||||
|
if ($context instanceof Context) {
|
||||||
|
$this->configuration->setContext($context);
|
||||||
|
} else {
|
||||||
|
$this->configuration->getContext()->setOptions($context);
|
||||||
|
trigger_error('Calling Client.setContext with array is deprecated, use Context class.', E_USER_DEPRECATED);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current stream context.
|
||||||
|
* @return Context
|
||||||
|
* @deprecated Will be removed in future version, get from Configuration instead
|
||||||
|
*/
|
||||||
|
public function getContext(): Context
|
||||||
|
{
|
||||||
|
return $this->configuration->getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add header for handshake.
|
||||||
|
* @param string $name Header name
|
||||||
|
* @param string $content Header content
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addHeader(string $name, string $content): self
|
||||||
|
{
|
||||||
|
$this->headers[$name] = $content;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a middleware.
|
||||||
|
* @param MiddlewareInterface $middleware
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addMiddleware(MiddlewareInterface $middleware): self
|
||||||
|
{
|
||||||
|
$this->middlewares[] = $middleware;
|
||||||
|
if ($this->connection) {
|
||||||
|
$this->connection->addMiddleware($middleware);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Messaging operations ----------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message.
|
||||||
|
* @template T of Message
|
||||||
|
* @param T $message
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
public function send(Message $message): Message
|
||||||
|
{
|
||||||
|
return $this->connection()->pushMessage($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive message.
|
||||||
|
* Note that this operation will block reading.
|
||||||
|
* @return Message
|
||||||
|
*/
|
||||||
|
public function receive(): Message
|
||||||
|
{
|
||||||
|
return $this->connection()->pullMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Listener operations ------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start client listener.
|
||||||
|
* @throws ExceptionInterface On high level error
|
||||||
|
* @throws Throwable On low level error
|
||||||
|
*/
|
||||||
|
public function start(int|float|null $timeout = null): void
|
||||||
|
{
|
||||||
|
// Check if running
|
||||||
|
if ($this->running) {
|
||||||
|
$this->configuration->getLogger()->warning("[{scope}] Client is already running", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->running = true;
|
||||||
|
$reconnect = false;
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] Client is running", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$connection = $this->connection();
|
||||||
|
|
||||||
|
// Run handler
|
||||||
|
while ($this->running) {
|
||||||
|
try {
|
||||||
|
// Run attached handlers on selected streams
|
||||||
|
$this->runner->handle($timeout ?? $this->configuration->getTimeout());
|
||||||
|
|
||||||
|
if (!$connection->isConnected()) {
|
||||||
|
$this->running = false;
|
||||||
|
}
|
||||||
|
$connection->tick();
|
||||||
|
$this->dispatch('tick', [$this]);
|
||||||
|
} catch (CloseException $e) {
|
||||||
|
// Close connection
|
||||||
|
$connection->close($e->getCloseStatus(), $e->getMessage());
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
$this->dispatch('error', [$this, $connection, $e]);
|
||||||
|
} catch (ReconnectException $e) {
|
||||||
|
// Reconnect connection
|
||||||
|
$reconnect = true;
|
||||||
|
if ($uri = $e->getUri()) {
|
||||||
|
$this->socketUri = $uri;
|
||||||
|
}
|
||||||
|
$connection->close();
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
$this->dispatch('error', [$this, $connection, $e]);
|
||||||
|
} catch (ExceptionInterface $e) {
|
||||||
|
$this->disconnect();
|
||||||
|
$this->running = false;
|
||||||
|
|
||||||
|
// Low-level error
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
$this->dispatch('error', [$this, null, $e]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->disconnect();
|
||||||
|
$this->running = false;
|
||||||
|
|
||||||
|
// Crash it
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
gc_collect_cycles(); // Collect garbage
|
||||||
|
|
||||||
|
if ($reconnect && !$connection->isConnected()) {
|
||||||
|
$reconnect = false;
|
||||||
|
$this->running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop client listener (resumable).
|
||||||
|
*/
|
||||||
|
public function stop(): void
|
||||||
|
{
|
||||||
|
$this->running = false;
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] Client is stopped", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If client is running (accepting messages).
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool
|
||||||
|
{
|
||||||
|
return $this->running;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Connection management ---------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If Client has active connection.
|
||||||
|
* @return bool True if active connection.
|
||||||
|
*/
|
||||||
|
public function isConnected(): bool
|
||||||
|
{
|
||||||
|
return $this->connection && $this->connection->isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If Client is readable.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return $this->connection && $this->connection->isReadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If Client is writable.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return $this->connection && $this->connection->isWritable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to server and perform upgrade.
|
||||||
|
* @throws ClientException On failed connection
|
||||||
|
*/
|
||||||
|
public function connect(): void
|
||||||
|
{
|
||||||
|
$this->disconnect();
|
||||||
|
|
||||||
|
$hostUri = (new Uri())
|
||||||
|
->withScheme(match ($this->socketUri->getScheme()) {
|
||||||
|
'ws', 'http' => 'tcp',
|
||||||
|
'wss', 'https' => 'ssl',
|
||||||
|
default => throw new ClientException("Invalid socket scheme: {$this->socketUri->getScheme()}")
|
||||||
|
})
|
||||||
|
->withHost($this->socketUri->getHost(Uri::IDN_ENCODE))
|
||||||
|
->withPort($this->socketUri->getPort(Uri::REQUIRE_PORT));
|
||||||
|
|
||||||
|
$stream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = $this->streamFactory->createSocketClient($hostUri, $this->configuration->getContext());
|
||||||
|
$client->setPersistent($this->configuration->isPersistent());
|
||||||
|
$client->setTimeout($this->configuration->getTimeout());
|
||||||
|
$stream = $client->connect();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$error = "Could not open socket to \"{$hostUri}\": {$e->getMessage()}";
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
throw new ClientException($error);
|
||||||
|
}
|
||||||
|
$this->connection = $connection = new Connection(
|
||||||
|
$stream,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
$hostUri->getScheme() === 'ssl',
|
||||||
|
$this->httpFactory,
|
||||||
|
clone $this->configuration
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->runner->attach($this->connection, function (Runner $runner, Connection $connection) {
|
||||||
|
try {
|
||||||
|
// Read from connection
|
||||||
|
$message = $connection->pullMessage();
|
||||||
|
$this->dispatch($message->getOpcode(), [$this, $connection, $message]);
|
||||||
|
} catch (MessageLevelInterface $e) {
|
||||||
|
// Error, but keep connection open
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
$this->dispatch('error', [$this, $connection, $e]);
|
||||||
|
} catch (ConnectionLevelInterface $e) {
|
||||||
|
// Error, disconnect connection
|
||||||
|
$this->disconnect();
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
$this->dispatch('error', [$this, $connection, $e]);
|
||||||
|
}
|
||||||
|
}, $this->connection->getIdentity());
|
||||||
|
|
||||||
|
foreach ($this->middlewares as $middleware) {
|
||||||
|
$connection->addMiddleware($middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->isConnected()) {
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] Invalid stream on {uri}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'uri' => $hostUri,
|
||||||
|
]);
|
||||||
|
throw new ClientException("Invalid stream on \"{$hostUri}\".");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!$this->configuration->isPersistent() || $stream->tell() == 0) {
|
||||||
|
/** @throws ReconnectException */
|
||||||
|
$response = $this->performHandshake($this->socketUri, $connection);
|
||||||
|
}
|
||||||
|
} catch (ReconnectException $e) {
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
if ($uri = $e->getUri()) {
|
||||||
|
$this->socketUri = $uri;
|
||||||
|
}
|
||||||
|
$this->connect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] Client connected to {uri}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'uri' => $this->socketUri,
|
||||||
|
]);
|
||||||
|
$this->dispatch('handshake', [
|
||||||
|
$this,
|
||||||
|
$connection,
|
||||||
|
$connection->getHandshakeRequest(),
|
||||||
|
$connection->getHandshakeResponse(),
|
||||||
|
]);
|
||||||
|
$this->dispatch('connect', [$this, $connection, $connection->getHandshakeResponse()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from server.
|
||||||
|
*/
|
||||||
|
public function disconnect(): void
|
||||||
|
{
|
||||||
|
if ($this->connection) {
|
||||||
|
$this->runner->detach($this->connection->getIdentity());
|
||||||
|
}
|
||||||
|
if ($this->connection && $this->isConnected()) {
|
||||||
|
$this->connection->disconnect();
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] Client disconnected", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $this->connection->getIdentity(),
|
||||||
|
]);
|
||||||
|
$this->dispatch('disconnect', [$this, $this->connection]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Connection wrapper methods ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name of local socket, or null if not connected.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getName(): string|null
|
||||||
|
{
|
||||||
|
return $this->isConnected() ? $this->connection?->getName() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name of remote socket, or null if not connected.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getRemoteName(): string|null
|
||||||
|
{
|
||||||
|
return $this->isConnected() ? $this->connection?->getRemoteName() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get meta value on connection.
|
||||||
|
* @param string $key Meta key
|
||||||
|
* @return mixed Meta value
|
||||||
|
* @deprecated Will be removed in v4
|
||||||
|
*/
|
||||||
|
public function getMeta(string $key): mixed
|
||||||
|
{
|
||||||
|
trigger_error('Client.getMeta is deprecated and will be removed in v4.', E_USER_DEPRECATED);
|
||||||
|
return $this->isConnected() ? $this->connection?->getMeta($key) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Response for handshake procedure.
|
||||||
|
* @return ResponseInterface|null Handshake.
|
||||||
|
*/
|
||||||
|
public function getHandshakeResponse(): ResponseInterface|null
|
||||||
|
{
|
||||||
|
return $this->connection ? $this->connection->getHandshakeResponse() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Internal helper methods -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform upgrade handshake on new connections.
|
||||||
|
* @throws HandshakeException On failed handshake
|
||||||
|
*/
|
||||||
|
protected function performHandshake(Uri $uri, Connection $connection): ResponseInterface
|
||||||
|
{
|
||||||
|
// Generate the WebSocket key.
|
||||||
|
$key = $this->generateKey();
|
||||||
|
|
||||||
|
$request = $this->httpFactory->createRequest('GET', $uri);
|
||||||
|
|
||||||
|
$request = $request
|
||||||
|
->withHeader('User-Agent', 'websocket-client-php')
|
||||||
|
->withHeader('Connection', 'Upgrade')
|
||||||
|
->withHeader('Upgrade', 'websocket')
|
||||||
|
->withHeader('Sec-WebSocket-Key', $key)
|
||||||
|
->withHeader('Sec-WebSocket-Version', '13');
|
||||||
|
|
||||||
|
// Handle basic authentication.
|
||||||
|
if ($userinfo = $uri->getUserInfo(Uri::URI_DECODE)) {
|
||||||
|
$request = $request->withHeader('Authorization', 'Basic ' . base64_encode($userinfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add and override with headers.
|
||||||
|
foreach ($this->headers as $name => $content) {
|
||||||
|
$request = $request->withHeader($name, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var RequestInterface */
|
||||||
|
$request = $connection->pushHttp($request);
|
||||||
|
/** @var ResponseInterface */
|
||||||
|
$response = $connection->pullHttp();
|
||||||
|
if ($response->getStatusCode() != 101) {
|
||||||
|
throw new HandshakeException("Invalid status code {$response->getStatusCode()}.", $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($response->getHeaderLine('Sec-WebSocket-Accept'))) {
|
||||||
|
throw new HandshakeException(
|
||||||
|
"Connection to '{$uri}' failed: Server sent invalid upgrade response.",
|
||||||
|
$response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$responseKey = trim($response->getHeaderLine('Sec-WebSocket-Accept'));
|
||||||
|
$expectedKey = base64_encode(
|
||||||
|
pack('H*', sha1($key . Constant::GUID))
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($responseKey !== $expectedKey) {
|
||||||
|
throw new HandshakeException("Server sent bad upgrade response.", $response);
|
||||||
|
}
|
||||||
|
} catch (HandshakeException $e) {
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->configuration->getLogger()->debug("[{scope}] Handshake on {path}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'client' => $this->identity,
|
||||||
|
'connection' => $connection->getIdentity(),
|
||||||
|
'path' => $uri->getPath(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$connection->setHandshakeRequest($request);
|
||||||
|
$connection->setHandshakeResponse($response);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random string for WebSocket key.
|
||||||
|
* @return string Random string
|
||||||
|
*/
|
||||||
|
protected function generateKey(): string
|
||||||
|
{
|
||||||
|
$key = '';
|
||||||
|
for ($i = 0; $i < 16; $i++) {
|
||||||
|
$key .= chr(rand(33, 126));
|
||||||
|
}
|
||||||
|
return base64_encode($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure URI instance to use in client.
|
||||||
|
* @param UriInterface|string $uri A ws/wss-URI
|
||||||
|
* @return Uri
|
||||||
|
* @throws BadUriException On invalid URI
|
||||||
|
*/
|
||||||
|
protected function parseUri(UriInterface|string $uri): Uri
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($uri instanceof Uri) {
|
||||||
|
$uriInstance = $uri;
|
||||||
|
} elseif ($uri instanceof UriInterface) {
|
||||||
|
$uriInstance = new Uri("{$uri}");
|
||||||
|
} else {
|
||||||
|
$uriInstance = new Uri($uri);
|
||||||
|
}
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
throw new BadUriException("Invalid URI '{$uri}' provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!in_array($uriInstance->getScheme(), ['ws', 'wss'])) {
|
||||||
|
throw new BadUriException("Invalid URI scheme, must be 'ws' or 'wss'.");
|
||||||
|
}
|
||||||
|
if (!$uriInstance->getHost()) {
|
||||||
|
throw new BadUriException("Invalid URI host.");
|
||||||
|
}
|
||||||
|
$uriInstance = $uriInstance->withPath($uriInstance->getPath(), Uri::ABSOLUTE_PATH | Uri::NORMALIZE_PATH);
|
||||||
|
return $uriInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function connection(): Connection
|
||||||
|
{
|
||||||
|
if (!$this->isConnected()) {
|
||||||
|
$this->connect();
|
||||||
|
}
|
||||||
|
/** @var Connection */
|
||||||
|
$connection = $this->connection;
|
||||||
|
return $connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2014-2026 Textalk and contributors.
|
||||||
|
* This file is part of Websocket PHP and is free software under the ISC License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WebSocket;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Phrity\Net\Context;
|
||||||
|
use Psr\Log\{
|
||||||
|
LoggerAwareInterface,
|
||||||
|
LoggerInterface,
|
||||||
|
NullLogger,
|
||||||
|
};
|
||||||
|
use Stringable;
|
||||||
|
use WebSocket\Trait\StringableTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket\Connection class.
|
||||||
|
* A client/server connection, wrapping socket stream.
|
||||||
|
*/
|
||||||
|
class Configuration implements LoggerAwareInterface, Stringable
|
||||||
|
{
|
||||||
|
use StringableTrait;
|
||||||
|
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
private Context $context;
|
||||||
|
/** @var int<0, max>|float $timeout */
|
||||||
|
private int|float $timeout;
|
||||||
|
/** @var int<1, max> $frameSize */
|
||||||
|
private int $frameSize;
|
||||||
|
private bool $persistent;
|
||||||
|
/** @var int<1, max>|null $maxConnections */
|
||||||
|
private int|null $maxConnections;
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Magic methods ------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LoggerInterface|null $logger
|
||||||
|
* @param Context|null $context
|
||||||
|
* @param int<0, max>|float $timeout
|
||||||
|
* @param int<1, max> $frameSize
|
||||||
|
* @param bool $persistent
|
||||||
|
* @param int<1, max>|null $maxConnections
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
LoggerInterface|null $logger = null,
|
||||||
|
Context|null $context = null,
|
||||||
|
int|float|null $timeout = null,
|
||||||
|
int|null $frameSize = null,
|
||||||
|
bool|null $persistent = null,
|
||||||
|
int|null $maxConnections = null,
|
||||||
|
) {
|
||||||
|
$this->setLogger($logger ?? new NullLogger());
|
||||||
|
$this->setContext($context ?? new Context());
|
||||||
|
$this->setTimeout($timeout ?? 60);
|
||||||
|
$this->setFrameSize($frameSize ?? 4096);
|
||||||
|
$this->setPersistent($persistent ?? false);
|
||||||
|
$this->setMaxConnections($maxConnections);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->stringable('');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Logger methods ----------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return LoggerInterface $logger
|
||||||
|
*/
|
||||||
|
public function getLogger(): LoggerInterface
|
||||||
|
{
|
||||||
|
return $this->logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LoggerInterface $logger
|
||||||
|
*/
|
||||||
|
public function setLogger(LoggerInterface $logger): void
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Context methods ---------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Context
|
||||||
|
*/
|
||||||
|
public function getContext(): Context
|
||||||
|
{
|
||||||
|
return $this->context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Context $context Context or options as array
|
||||||
|
*/
|
||||||
|
public function setContext(Context $context): void
|
||||||
|
{
|
||||||
|
$this->context = $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Timeout methods ---------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>|float Timeout in seconds
|
||||||
|
*/
|
||||||
|
public function getTimeout(): int|float
|
||||||
|
{
|
||||||
|
return $this->timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int<0, max>|float $timeout Timeout in seconds
|
||||||
|
* @throws InvalidArgumentException If invalid timeout provided
|
||||||
|
*/
|
||||||
|
public function setTimeout(int|float $timeout): void
|
||||||
|
{
|
||||||
|
if ($timeout < 0) {
|
||||||
|
throw new InvalidArgumentException("Invalid timeout '{$timeout}' provided");
|
||||||
|
}
|
||||||
|
$this->timeout = $timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- FrameSize methods -------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<1, max> Frame size in bytes
|
||||||
|
*/
|
||||||
|
public function getFrameSize(): int
|
||||||
|
{
|
||||||
|
return $this->frameSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int<1, max> $frameSize Max frame payload size in bytes
|
||||||
|
* @throws InvalidArgumentException If invalid frameSize provided
|
||||||
|
*/
|
||||||
|
public function setFrameSize(int $frameSize): void
|
||||||
|
{
|
||||||
|
if ($frameSize < 1) {
|
||||||
|
throw new InvalidArgumentException("Invalid frameSize '{$frameSize}' provided");
|
||||||
|
}
|
||||||
|
$this->frameSize = $frameSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Persistent methods (Client only) ----------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool $persistent
|
||||||
|
*/
|
||||||
|
public function isPersistent(): bool
|
||||||
|
{
|
||||||
|
return $this->persistent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $persistent True for persistent connection
|
||||||
|
*/
|
||||||
|
public function setPersistent(bool $persistent): void
|
||||||
|
{
|
||||||
|
$this->persistent = $persistent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Max connections (Server only) -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<1, max> Max connections allowed
|
||||||
|
*/
|
||||||
|
public function getMaxConnections(): int|null
|
||||||
|
{
|
||||||
|
return $this->maxConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int<1, max>|null $maxConnections
|
||||||
|
* @throws InvalidArgumentException If invalid number provided
|
||||||
|
*/
|
||||||
|
public function setMaxConnections(int|null $maxConnections): void
|
||||||
|
{
|
||||||
|
if ($maxConnections !== null && $maxConnections < 1) {
|
||||||
|
throw new InvalidArgumentException("Invalid maxConnections '{$maxConnections}' provided");
|
||||||
|
}
|
||||||
|
$this->maxConnections = $maxConnections;
|
||||||
|
}
|
||||||
|
}
|
||||||
+498
@@ -0,0 +1,498 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2014-2026 Textalk and contributors.
|
||||||
|
* This file is part of Websocket PHP and is free software under the ISC License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WebSocket;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Phrity\Http\HttpFactory;
|
||||||
|
use Phrity\Net\{
|
||||||
|
Context,
|
||||||
|
SocketStream,
|
||||||
|
StreamContainerInterface,
|
||||||
|
};
|
||||||
|
use Psr\Http\Message\{
|
||||||
|
MessageInterface,
|
||||||
|
RequestInterface,
|
||||||
|
ResponseFactoryInterface,
|
||||||
|
ResponseInterface,
|
||||||
|
ServerRequestFactoryInterface,
|
||||||
|
UriFactoryInterface,
|
||||||
|
};
|
||||||
|
use Psr\Log\{
|
||||||
|
LoggerAwareInterface,
|
||||||
|
LoggerInterface,
|
||||||
|
};
|
||||||
|
use Stringable;
|
||||||
|
use Throwable;
|
||||||
|
use WebSocket\Frame\FrameHandler;
|
||||||
|
use WebSocket\Http\HttpHandler;
|
||||||
|
use WebSocket\Exception\{
|
||||||
|
ConnectionClosedException,
|
||||||
|
ConnectionFailureException,
|
||||||
|
ConnectionTimeoutException,
|
||||||
|
ExceptionInterface,
|
||||||
|
ReconnectException,
|
||||||
|
};
|
||||||
|
use WebSocket\Message\{
|
||||||
|
Message,
|
||||||
|
MessageHandler
|
||||||
|
};
|
||||||
|
use WebSocket\Middleware\{
|
||||||
|
MiddlewareHandler,
|
||||||
|
MiddlewareInterface
|
||||||
|
};
|
||||||
|
use WebSocket\Runtime\IdentityInterface;
|
||||||
|
use WebSocket\Trait\{
|
||||||
|
ConfigurationTrait,
|
||||||
|
SendMethodsTrait,
|
||||||
|
StringableTrait
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket\Connection class.
|
||||||
|
* A client/server connection, wrapping socket stream.
|
||||||
|
*/
|
||||||
|
class Connection implements IdentityInterface, LoggerAwareInterface, StreamContainerInterface, Stringable
|
||||||
|
{
|
||||||
|
use ConfigurationTrait;
|
||||||
|
use SendMethodsTrait;
|
||||||
|
use StringableTrait;
|
||||||
|
|
||||||
|
private const SCOPE = 'connection';
|
||||||
|
|
||||||
|
private SocketStream $stream;
|
||||||
|
private HttpHandler $httpHandler;
|
||||||
|
private MessageHandler $messageHandler;
|
||||||
|
private MiddlewareHandler $middlewareHandler;
|
||||||
|
private string $localName;
|
||||||
|
private string $remoteName;
|
||||||
|
private RequestInterface|null $handshakeRequest = null;
|
||||||
|
private ResponseInterface|null $handshakeResponse = null;
|
||||||
|
/** @var array<string, mixed> $meta */
|
||||||
|
private array $meta = [];
|
||||||
|
/** @var non-empty-string $identity */
|
||||||
|
private string $identity = '*/connection/<unconnected>';
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Magic methods ------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
SocketStream $stream,
|
||||||
|
bool $pushMasked,
|
||||||
|
bool $pullMaskedRequired,
|
||||||
|
bool $ssl = false,
|
||||||
|
HttpFactory|null $httpFactory = null,
|
||||||
|
Configuration|null $configuration = null,
|
||||||
|
) {
|
||||||
|
$this->stream = $stream;
|
||||||
|
$this->httpHandler = new HttpHandler($this->stream, $ssl, $httpFactory);
|
||||||
|
$this->messageHandler = new MessageHandler(new FrameHandler($this->stream, $pushMasked, $pullMaskedRequired));
|
||||||
|
$this->middlewareHandler = new MiddlewareHandler($this->messageHandler, $this->httpHandler);
|
||||||
|
$this->localName = $this->stream->getLocalName() ?? '<unknown>';
|
||||||
|
$this->remoteName = $this->stream->getRemoteName() ?? '<unknown>';
|
||||||
|
$this->identity = sprintf(
|
||||||
|
'*/connection/%s/%s',
|
||||||
|
$this->getIdentityPart($this->localName),
|
||||||
|
$this->getIdentityPart($this->remoteName),
|
||||||
|
);
|
||||||
|
$this->initConfiguration($configuration);
|
||||||
|
$this->stream->setTimeout($this->configuration->getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->stringable('%s:%s', $this->localName, $this->remoteName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Configuration ------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
public function getIdentity(): string
|
||||||
|
{
|
||||||
|
return $this->identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set logger.
|
||||||
|
* @param LoggerInterface $logger Logger implementation
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
*/
|
||||||
|
public function setLogger(LoggerInterface $logger): void
|
||||||
|
{
|
||||||
|
$this->configuration->setLogger($logger);
|
||||||
|
$this->messageHandler->setLogger($logger);
|
||||||
|
$this->middlewareHandler->setLogger($logger);
|
||||||
|
$this->configuration->getLogger()->debug("[{scope}] Setting logger: {logger}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'logger' => get_class($logger),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set time out on connection.
|
||||||
|
* @param int<0, max>|float $timeout Timeout part in seconds
|
||||||
|
* @return self
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
*/
|
||||||
|
public function setTimeout(int|float $timeout): self
|
||||||
|
{
|
||||||
|
$this->configuration->setTimeout($timeout);
|
||||||
|
$this->stream->setTimeout($timeout);
|
||||||
|
$this->configuration->getLogger()->debug("[{scope}] Setting timeout: {timeout} seconds", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'timeout' => $timeout,
|
||||||
|
]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timeout.
|
||||||
|
* @return int<0, max>|float Timeout in seconds.
|
||||||
|
* @deprecated Will be removed in future version, get from Configuration instead
|
||||||
|
*/
|
||||||
|
public function getTimeout(): int|float
|
||||||
|
{
|
||||||
|
return $this->configuration->getTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set frame size.
|
||||||
|
* @param int<1, max> $frameSize Frame size in bytes.
|
||||||
|
* @return self
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @deprecated Will be removed in future version, set on Configuration instead
|
||||||
|
*/
|
||||||
|
public function setFrameSize(int $frameSize): self
|
||||||
|
{
|
||||||
|
$this->configuration->setFrameSize($frameSize);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get frame size.
|
||||||
|
* @return int<1, max> Frame size in bytes
|
||||||
|
* @deprecated Will be removed in future version, get from Configuration instead
|
||||||
|
*/
|
||||||
|
public function getFrameSize(): int
|
||||||
|
{
|
||||||
|
return $this->configuration->getFrameSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current stream context.
|
||||||
|
* @return Context
|
||||||
|
*/
|
||||||
|
public function getContext(): Context
|
||||||
|
{
|
||||||
|
return $this->stream->getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a middleware.
|
||||||
|
* @param MiddlewareInterface $middleware
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addMiddleware(MiddlewareInterface $middleware): self
|
||||||
|
{
|
||||||
|
$this->middlewareHandler->add($middleware);
|
||||||
|
$this->configuration->getLogger()->debug("[{scope}] Added middleware: {middleware}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'middleware' => $middleware,
|
||||||
|
]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Connection management ---------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If connected to stream.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isConnected(): bool
|
||||||
|
{
|
||||||
|
return $this->stream->isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If connection is readable.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return $this->stream->isReadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If connection is writable.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return $this->stream->isWritable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close connection stream.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function disconnect(): self
|
||||||
|
{
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] Closing connection", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
]);
|
||||||
|
$this->stream->close();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close connection stream reading.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function closeRead(): self
|
||||||
|
{
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] Closing further reading", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
]);
|
||||||
|
$this->stream->closeRead();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close connection stream writing.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function closeWrite(): self
|
||||||
|
{
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] Closing further writing", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
]);
|
||||||
|
$this->stream->closeWrite();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Connection state --------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name of local socket, or null if not connected.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getName(): string|null
|
||||||
|
{
|
||||||
|
return $this->localName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name of remote socket, or null if not connected.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getRemoteName(): string|null
|
||||||
|
{
|
||||||
|
return $this->remoteName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set meta value on connection.
|
||||||
|
* @param string $key Meta key
|
||||||
|
* @param mixed $value Meta value
|
||||||
|
*/
|
||||||
|
public function setMeta(string $key, mixed $value): void
|
||||||
|
{
|
||||||
|
$this->meta[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get meta value on connection.
|
||||||
|
* @param string $key Meta key
|
||||||
|
* @return mixed Meta value
|
||||||
|
*/
|
||||||
|
public function getMeta(string $key): mixed
|
||||||
|
{
|
||||||
|
return $this->meta[$key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tick operation on connection.
|
||||||
|
*/
|
||||||
|
public function tick(): void
|
||||||
|
{
|
||||||
|
$this->middlewareHandler->processTick($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- WebSocket Message methods ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message.
|
||||||
|
* @template T of Message
|
||||||
|
* @param T $message
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
public function send(Message $message): Message
|
||||||
|
{
|
||||||
|
return $this->pushMessage($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a message to stream.
|
||||||
|
* @template T of Message
|
||||||
|
* @param T $message
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
public function pushMessage(Message $message): Message
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
/** @throws Throwable */
|
||||||
|
return $this->middlewareHandler->processOutgoing($this, $message);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->throwException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull a message from stream
|
||||||
|
* @throws ExceptionInterface
|
||||||
|
*/
|
||||||
|
public function pullMessage(): Message
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
/** @throws Throwable */
|
||||||
|
return $this->middlewareHandler->processIncoming($this);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->throwException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- HTTP Message methods ----------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
public function pushHttp(MessageInterface $message): MessageInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
/** @throws Throwable */
|
||||||
|
return $this->middlewareHandler->processHttpOutgoing($this, $message);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->throwException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pullHttp(): MessageInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
/** @throws Throwable */
|
||||||
|
return $this->middlewareHandler->processHttpIncoming($this);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->throwException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHandshakeRequest(RequestInterface $request): self
|
||||||
|
{
|
||||||
|
$this->handshakeRequest = $request;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHandshakeRequest(): RequestInterface|null
|
||||||
|
{
|
||||||
|
return $this->handshakeRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHandshakeResponse(ResponseInterface $response): self
|
||||||
|
{
|
||||||
|
$this->handshakeResponse = $response;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHandshakeResponse(): ResponseInterface|null
|
||||||
|
{
|
||||||
|
return $this->handshakeResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStream(): SocketStream
|
||||||
|
{
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Internal helper methods -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ReconnectException
|
||||||
|
* @throws ExceptionInterface
|
||||||
|
* @throws ConnectionTimeoutException
|
||||||
|
* @throws ConnectionClosedException
|
||||||
|
* @throws ConnectionFailureException
|
||||||
|
*/
|
||||||
|
protected function throwException(Throwable $e): never
|
||||||
|
{
|
||||||
|
// Internal exceptions are handled and re-thrown
|
||||||
|
if ($e instanceof ReconnectException) {
|
||||||
|
$this->configuration->getLogger()->info("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
if ($e instanceof ExceptionInterface) {
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
// External exceptions are converted to internal
|
||||||
|
if ($this->isConnected()) {
|
||||||
|
$meta = $this->stream->getMetadata();
|
||||||
|
$json = json_encode($meta);
|
||||||
|
if (!empty($meta['timed_out'])) {
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'meta' => $meta
|
||||||
|
]);
|
||||||
|
throw new ConnectionTimeoutException();
|
||||||
|
}
|
||||||
|
if (!empty($meta['eof'])) {
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'meta' => $meta
|
||||||
|
]);
|
||||||
|
throw new ConnectionClosedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->configuration->getLogger()->error("[{scope}] {message}", [
|
||||||
|
'scope' => self::SCOPE,
|
||||||
|
'connection' => $this->identity,
|
||||||
|
'exception' => $e,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
throw new ConnectionFailureException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getIdentityPart(string $source): string
|
||||||
|
{
|
||||||
|
preg_match('/([0-9]+)$/', $source, $result);
|
||||||
|
return empty($result) ? $source : array_shift($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user