init
This commit is contained in:
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"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": "^7.4 | ^8.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.0 | ^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0 | ^10.0",
|
||||
"php-coveralls/php-coveralls": "^2.0",
|
||||
"squizlabs/php_codesniffer": "^3.0"
|
||||
}
|
||||
}
|
||||
Vendored
+486
@@ -0,0 +1,486 @@
|
||||
<?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 Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Net\Uri class.
|
||||
*/
|
||||
class Uri implements 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; // IDNA-convert host
|
||||
|
||||
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]*))?$!';
|
||||
|
||||
private static $port_defaults = [
|
||||
'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 $scheme;
|
||||
private $authority;
|
||||
private $host;
|
||||
private $port;
|
||||
private $user;
|
||||
private $pass;
|
||||
private $path;
|
||||
private $query;
|
||||
private $fragment;
|
||||
|
||||
/**
|
||||
* Create new URI instance using a string
|
||||
* @param string $uri_string URI as string
|
||||
* @throws \InvalidArgumentException If the given URI cannot be parsed
|
||||
*/
|
||||
public function __construct(string $uri_string = '', int $flags = 0)
|
||||
{
|
||||
$this->parse($uri_string);
|
||||
}
|
||||
|
||||
|
||||
// ---------- PSR-7 getters ---------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retrieve the scheme component of the URI.
|
||||
* @return string The URI scheme
|
||||
*/
|
||||
public function getScheme(int $flags = 0): string
|
||||
{
|
||||
return $this->getComponent('scheme') ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authority component of the URI.
|
||||
* @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 ($this->isEmpty($host)) {
|
||||
return '';
|
||||
}
|
||||
$userinfo = $this->formatComponent($this->getUserInfo(), '', '@');
|
||||
$port = $this->formatComponent($this->getPort($flags), ':');
|
||||
return "{$userinfo}{$host}{$port}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the user information component of the URI.
|
||||
* @return string The URI user information, in "username[:password]" format
|
||||
*/
|
||||
public function getUserInfo(int $flags = 0): string
|
||||
{
|
||||
$user = $this->formatComponent($this->getComponent('user'));
|
||||
$pass = $this->formatComponent($this->getComponent('pass'), ':');
|
||||
return $this->isEmpty($user) ? '' : "{$user}{$pass}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the host component of the URI.
|
||||
* @return string The URI host
|
||||
*/
|
||||
public function getHost(int $flags = 0): string
|
||||
{
|
||||
$host = $this->getComponent('host') ?? '';
|
||||
if ($flags & self::IDNA) {
|
||||
$host = $this->idna($host);
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the port component of the URI.
|
||||
* @return null|int The URI port
|
||||
*/
|
||||
public function getPort(int $flags = 0): ?int
|
||||
{
|
||||
$port = $this->getComponent('port');
|
||||
$scheme = $this->getComponent('scheme');
|
||||
$default = isset(self::$port_defaults[$scheme]) ? self::$port_defaults[$scheme] : null;
|
||||
if ($flags & self::REQUIRE_PORT) {
|
||||
return !$this->isEmpty($port) ? $port : $default;
|
||||
}
|
||||
return $this->isEmpty($port) || $port === $default ? null : $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the path component of the URI.
|
||||
* @return string The URI path
|
||||
*/
|
||||
public function getPath(int $flags = 0): string
|
||||
{
|
||||
$path = $this->getComponent('path') ?? '';
|
||||
if ($flags & self::NORMALIZE_PATH) {
|
||||
$path = $this->normalizePath($path);
|
||||
}
|
||||
if ($flags & self::ABSOLUTE_PATH && substr($path, 0, 1) !== '/') {
|
||||
$path = "/{$path}";
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the query string of the URI.
|
||||
* @return string The URI query string
|
||||
*/
|
||||
public function getQuery(int $flags = 0): string
|
||||
{
|
||||
return $this->getComponent('query') ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the fragment component of the URI.
|
||||
* @return string The URI fragment
|
||||
*/
|
||||
public function getFragment(int $flags = 0): string
|
||||
{
|
||||
return $this->getComponent('fragment') ?? '';
|
||||
}
|
||||
|
||||
|
||||
// ---------- PSR-7 setters ---------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return an instance with the specified scheme.
|
||||
* @param string $scheme The scheme to use with the new instance
|
||||
* @return static A new instance with the specified scheme
|
||||
* @throws \InvalidArgumentException for invalid schemes
|
||||
* @throws \InvalidArgumentException for unsupported schemes
|
||||
*/
|
||||
public function withScheme($scheme, int $flags = 0): UriInterface
|
||||
{
|
||||
$clone = clone $this;
|
||||
if ($flags & self::REQUIRE_PORT) {
|
||||
$clone->setComponent('port', $this->getPort(self::REQUIRE_PORT));
|
||||
$default = isset(self::$port_defaults[$scheme]) ? self::$port_defaults[$scheme] : null;
|
||||
}
|
||||
$clone->setComponent('scheme', $scheme);
|
||||
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
|
||||
* @return static A new instance with the specified user information
|
||||
*/
|
||||
public function withUserInfo($user, $password = null, int $flags = 0): UriInterface
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->setComponent('user', $user);
|
||||
$clone->setComponent('pass', $password);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified host.
|
||||
* @param string $host The hostname to use with the new instance
|
||||
* @return static A new instance with the specified host
|
||||
* @throws \InvalidArgumentException for invalid hostnames
|
||||
*/
|
||||
public function withHost($host, int $flags = 0): UriInterface
|
||||
{
|
||||
$clone = clone $this;
|
||||
if ($flags & self::IDNA) {
|
||||
$host = $this->idna($host);
|
||||
}
|
||||
$clone->setComponent('host', $host);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified port.
|
||||
* @param null|int $port The port to use with the new instance
|
||||
* @return static A new instance with the specified port
|
||||
* @throws \InvalidArgumentException for invalid ports
|
||||
*/
|
||||
public function withPort($port, int $flags = 0): UriInterface
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->setComponent('port', $port);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified path.
|
||||
* @param string $path The path to use with the new instance
|
||||
* @return static A new instance with the specified path
|
||||
* @throws \InvalidArgumentException for invalid paths
|
||||
*/
|
||||
public function withPath($path, int $flags = 0): UriInterface
|
||||
{
|
||||
$clone = clone $this;
|
||||
if ($flags & self::NORMALIZE_PATH) {
|
||||
$path = $this->normalizePath($path);
|
||||
}
|
||||
if ($flags & self::ABSOLUTE_PATH && substr($path, 0, 1) !== '/') {
|
||||
$path = "/{$path}";
|
||||
}
|
||||
$clone->setComponent('path', $path);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified query string.
|
||||
* @param string $query The query string to use with the new instance
|
||||
* @return static A new instance with the specified query string
|
||||
* @throws \InvalidArgumentException for invalid query strings
|
||||
*/
|
||||
public function withQuery($query, int $flags = 0): UriInterface
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->setComponent('query', $query);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified URI fragment.
|
||||
* @param string $fragment The fragment to use with the new instance
|
||||
* @return static A new instance with the specified fragment
|
||||
*/
|
||||
public function withFragment($fragment, int $flags = 0): UriInterface
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->setComponent('fragment', $fragment);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
|
||||
// ---------- PSR-7 string ----------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return the string representation as a URI reference.
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
|
||||
// ---------- Extensions ------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return the string representation as a URI reference.
|
||||
* @return string
|
||||
*/
|
||||
public function toString(int $flags = 0): string
|
||||
{
|
||||
$scheme = $this->formatComponent($this->getComponent('scheme'), '', ':');
|
||||
$authority = $this->authority ? "//{$this->formatComponent($this->getAuthority($flags))}" : '';
|
||||
$path_flags = ($this->authority && $this->path ? self::ABSOLUTE_PATH : 0) | $flags;
|
||||
$path = $this->formatComponent($this->getPath($path_flags));
|
||||
$query = $this->formatComponent($this->getComponent('query'), '?');
|
||||
$fragment = $this->formatComponent($this->getComponent('fragment'), '#');
|
||||
return "{$scheme}{$authority}{$path}{$query}{$fragment}";
|
||||
}
|
||||
|
||||
|
||||
// ---------- Private helper methods ------------------------------------------------------------------------------
|
||||
|
||||
private function parse(string $uri_string = ''): void
|
||||
{
|
||||
if ($uri_string === '') {
|
||||
return;
|
||||
}
|
||||
preg_match(self::RE_MAIN, $uri_string, $main);
|
||||
$this->authority = !empty($main['authorityc']);
|
||||
$this->setComponent('scheme', isset($main['schemec']) ? $main['scheme'] : '');
|
||||
$this->setComponent('path', isset($main['path']) ? $main['path'] : '');
|
||||
$this->setComponent('query', isset($main['queryc']) ? $main['query'] : '');
|
||||
$this->setComponent('fragment', isset($main['fragmentc']) ? $main['fragment'] : '');
|
||||
if ($this->authority) {
|
||||
preg_match(self::RE_AUTH, $main['authority'], $auth);
|
||||
if (empty($auth) && $main['authority'] !== '') {
|
||||
throw new InvalidArgumentException("Invalid 'authority'.");
|
||||
}
|
||||
if ($this->isEmpty($auth['host']) && !$this->isEmpty($auth['user'])) {
|
||||
throw new InvalidArgumentException("Invalid 'authority'.");
|
||||
}
|
||||
$this->setComponent('user', isset($auth['user']) ? $auth['user'] : '');
|
||||
$this->setComponent('pass', isset($auth['passc']) ? $auth['pass'] : '');
|
||||
$this->setComponent('host', isset($auth['host']) ? $auth['host'] : '');
|
||||
$this->setComponent('port', isset($auth['portc']) ? $auth['port'] : '');
|
||||
}
|
||||
}
|
||||
|
||||
private function encode(string $source, string $keep = ''): string
|
||||
{
|
||||
$exclude = "[^%\/:=&!\$'()*+,;@{$keep}]+";
|
||||
$exp = "/(%{$exclude})|({$exclude})/";
|
||||
return preg_replace_callback($exp, function ($matches) {
|
||||
if ($e = preg_match('/^(%[0-9a-fA-F]{2})/', $matches[0], $m)) {
|
||||
return substr($matches[0], 0, 3) . rawurlencode(substr($matches[0], 3));
|
||||
} else {
|
||||
return rawurlencode($matches[0]);
|
||||
}
|
||||
}, $source);
|
||||
}
|
||||
|
||||
private function setComponent(string $component, $value): void
|
||||
{
|
||||
$value = $this->parseCompontent($component, $value);
|
||||
$this->$component = $value;
|
||||
}
|
||||
|
||||
private function parseCompontent(string $component, $value)
|
||||
{
|
||||
if ($this->isEmpty($value)) {
|
||||
return null;
|
||||
}
|
||||
switch ($component) {
|
||||
case 'scheme':
|
||||
$this->assertString($component, $value);
|
||||
$this->assertpattern($component, $value, '/^[a-z][a-z0-9-+.]*$/i');
|
||||
return mb_strtolower($value);
|
||||
case 'host': // IP-literal / IPv4address / reg-name
|
||||
$this->assertString($component, $value);
|
||||
$this->authority = $this->authority || !$this->isEmpty($value);
|
||||
return mb_strtolower($value);
|
||||
case 'port':
|
||||
$this->assertInteger($component, $value);
|
||||
if ($value < 0 || $value > 65535) {
|
||||
throw new InvalidArgumentException("Invalid port number");
|
||||
}
|
||||
return (int)$value;
|
||||
case 'path':
|
||||
$this->assertString($component, $value);
|
||||
$value = $this->encode($value);
|
||||
return $value;
|
||||
case 'user':
|
||||
case 'pass':
|
||||
case 'query':
|
||||
case 'fragment':
|
||||
$this->assertString($component, $value);
|
||||
$value = $this->encode($value, '?');
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
private function getComponent(string $component)
|
||||
{
|
||||
return isset($this->$component) ? $this->$component : null;
|
||||
}
|
||||
|
||||
private function formatComponent($value, string $before = '', string $after = ''): string
|
||||
{
|
||||
return $this->isEmpty($value) ? '' : "{$before}{$value}{$after}";
|
||||
}
|
||||
|
||||
private function isEmpty($value): bool
|
||||
{
|
||||
return is_null($value) || $value === '';
|
||||
}
|
||||
|
||||
private function assertString(string $component, $value): void
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
throw new InvalidArgumentException("Invalid '{$component}': Should be a string");
|
||||
}
|
||||
}
|
||||
|
||||
private function assertInteger(string $component, $value): void
|
||||
{
|
||||
if (!is_numeric($value) || intval($value) != $value) {
|
||||
throw new InvalidArgumentException("Invalid '{$component}': Should be an integer");
|
||||
}
|
||||
}
|
||||
|
||||
private function assertPattern(string $component, string $value, string $pattern): void
|
||||
{
|
||||
if (preg_match($pattern, $value) == 0) {
|
||||
throw new InvalidArgumentException("Invalid '{$component}': Should match {$pattern}");
|
||||
}
|
||||
}
|
||||
|
||||
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 idna(string $value): string
|
||||
{
|
||||
if ($value === '' || !is_callable('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);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?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 Psr\Http\Message\{
|
||||
UriFactoryInterface,
|
||||
UriInterface
|
||||
};
|
||||
|
||||
/**
|
||||
* Net\UriFactory class.
|
||||
*/
|
||||
class UriFactory implements UriFactoryInterface
|
||||
{
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
name: Acceptance
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ["7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]
|
||||
runs-on: ubuntu-latest
|
||||
name: Unit test
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist
|
||||
- name: Test
|
||||
run: vendor/bin/phpunit
|
||||
|
||||
cs-check:
|
||||
runs-on: ubuntu-latest
|
||||
name: Code standard
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist
|
||||
- name: Code standard
|
||||
run: vendor/bin/phpcs --standard=PSR1,PSR12 --encoding=UTF-8 --report=full --colors src tests
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
name: Code coverage
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
coverage: xdebug
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist
|
||||
- name: Code coverage build
|
||||
run: XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover build/logs/clover.xml
|
||||
- name: Code coverage upload
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: vendor/bin/php-coveralls -v
|
||||
@@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
.phpunit.result.cache
|
||||
build/
|
||||
composer.lock
|
||||
composer.phar
|
||||
vendor/
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Default
|
||||
all: deps-install
|
||||
|
||||
|
||||
# DEPENDENCY MANAGEMENT
|
||||
|
||||
# Updates dependencies according to lock file
|
||||
deps-install: composer.phar
|
||||
./composer.phar --no-interaction install
|
||||
|
||||
# Updates dependencies according to json file
|
||||
deps-update: composer.phar
|
||||
./composer.phar self-update
|
||||
./composer.phar --no-interaction update
|
||||
|
||||
|
||||
# TESTS AND REPORTS
|
||||
|
||||
# Code standard check
|
||||
cs-check: composer.lock
|
||||
./vendor/bin/phpcs --standard=PSR1,PSR12 --encoding=UTF-8 --report=full --colors src tests
|
||||
|
||||
# Run tests
|
||||
test: composer.lock
|
||||
./vendor/bin/phpunit
|
||||
|
||||
# Run tests with clover coverage report
|
||||
coverage: composer.lock
|
||||
XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml
|
||||
./vendor/bin/php-coveralls -v
|
||||
|
||||
|
||||
# INITIAL INSTALL
|
||||
|
||||
# Ensures composer is installed
|
||||
composer.phar:
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
# Ensures composer is installed and dependencies loaded
|
||||
composer.lock: composer.phar
|
||||
./composer.phar --no-interaction install
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
[](https://github.com/sirn-se/phrity-util-errorhandler/actions)
|
||||
[](https://coveralls.io/github/sirn-se/phrity-util-errorhandler?branch=main)
|
||||
|
||||
# Error Handler utility
|
||||
|
||||
The PHP [error handling](https://www.php.net/manual/en/book.errorfunc.php) can be somewhat of a headache.
|
||||
Typically an application uses a system level [error handler](https://www.php.net/manual/en/function.set-error-handler.php) and/or suppressing errors using the `@` prefix.
|
||||
But those cases when your code need to act on triggered errors are more tricky.
|
||||
|
||||
This library provides two convenience methods to handle errors on code blocks, either by throwing exceptions or running callback code when an error occurs.
|
||||
|
||||
Current version supports PHP `^7.2|^8.0`.
|
||||
|
||||
## Installation
|
||||
|
||||
Install with [Composer](https://getcomposer.org/);
|
||||
```
|
||||
composer require phrity/util-errorhandler
|
||||
```
|
||||
|
||||
## The Error Handler
|
||||
|
||||
The class provides two main methods; `with()` and `withAll()`.
|
||||
The difference is that `with()` will act immediately on an error and abort further code execution, while `withAll()` will attempt to execute the entire code block before acting on errors that occurred.
|
||||
|
||||
### Throwing ErrorException
|
||||
|
||||
```php
|
||||
use Phrity\Util\ErrorHandler;
|
||||
|
||||
$handler = new ErrorHandler();
|
||||
$result = $handler->with(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
});
|
||||
$result = $handler->withAll(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
});
|
||||
```
|
||||
The examples above will run the callback code, but if an error occurs it will throw an [ErrorException](https://www.php.net/manual/en/class.errorexception.php).
|
||||
Error message and severity will be that of the triggering error.
|
||||
* `with()` will throw immediately when occured
|
||||
* `withAll()` will throw when code is complete; if more than one error occurred, the first will be thrown
|
||||
|
||||
### Throwing specified Throwable
|
||||
|
||||
```php
|
||||
use Phrity\Util\ErrorHandler;
|
||||
|
||||
$handler = new ErrorHandler();
|
||||
$result = $handler->with(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
}, new RuntimeException('A specified error'));
|
||||
$result = $handler->withAll(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
}, new RuntimeException('A specified error'));
|
||||
```
|
||||
The examples above will run the callback code, but if an error occurs it will throw provided Throwable.
|
||||
The thrown Throwable will have an [ErrorException](https://www.php.net/manual/en/class.errorexception.php) attached as `$previous`.
|
||||
* `with()` will throw immediately when occured
|
||||
* `withAll()` will throw when code is complete; if more than one error occurred, the first will be thrown
|
||||
|
||||
### Using callback
|
||||
|
||||
```php
|
||||
use Phrity\Util\ErrorHandler;
|
||||
|
||||
$handler = new ErrorHandler();
|
||||
$result = $handler->with(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
}, function (ErrorException $error) {
|
||||
// Code to handle error
|
||||
return $error_result;
|
||||
});
|
||||
$result = $handler->withAll(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
}, function (array $errors, $success_result) {
|
||||
// Code to handle errors
|
||||
return $error_result;
|
||||
});
|
||||
```
|
||||
The examples above will run the callback code, but if an error occurs it will call the error callback as well.
|
||||
* `with()` will run the error callback immediately when error occured; error callback expects an ErrorException instance
|
||||
* `withAll()` will run the error callback when code is complete; error callback expects an array of ErrorException and the returned result of code callback
|
||||
|
||||
### Filtering error types
|
||||
|
||||
Both `with()` and `withAll()` accepts error level(s) as last parameter.
|
||||
```php
|
||||
use Phrity\Util\ErrorHandler;
|
||||
|
||||
$handler = new ErrorHandler();
|
||||
$result = $handler->with(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
}, null, E_USER_ERROR);
|
||||
$result = $handler->withAll(function () {
|
||||
// Code to execute
|
||||
return $success_result;
|
||||
}, null, E_USER_ERROR & E_USER_WARNING);
|
||||
```
|
||||
Any value or combination of values accepted by [set_error_handler](https://www.php.net/manual/en/function.set-error-handler.php) is usable.
|
||||
Default is `E_ALL`. [List of constants](https://www.php.net/manual/en/errorfunc.constants.php).
|
||||
|
||||
### The global error handler
|
||||
|
||||
The class also has global `set()` and `restore()` methods.
|
||||
|
||||
```php
|
||||
use Phrity\Util\ErrorHandler;
|
||||
|
||||
$handler = new ErrorHandler();
|
||||
$handler->set(); // Throws ErrorException on error
|
||||
$handler->set(new RuntimeException('A specified error')); // Throws provided Throwable on error
|
||||
$handler->set(function (ErrorException $error) {
|
||||
// Code to handle errors
|
||||
return $error_result;
|
||||
}); // Runs callback on error
|
||||
$handler->restore(); // Restores error handler
|
||||
```
|
||||
|
||||
### Class synopsis
|
||||
|
||||
```php
|
||||
Phrity\Util\ErrorHandler {
|
||||
|
||||
/* Methods */
|
||||
public __construct()
|
||||
|
||||
public with(callable $callback, mixed $handling = null, int $levels = E_ALL) : mixed
|
||||
public withAll(callable $callback, mixed $handling = null, int $levels = E_ALL) : mixed
|
||||
public set($handling = null, int $levels = E_ALL) : mixed
|
||||
public restore() : bool
|
||||
}
|
||||
```
|
||||
|
||||
## Versions
|
||||
|
||||
| Version | PHP | |
|
||||
| --- | --- | --- |
|
||||
| `1.1` | `^7.4\|^8.0` | Some improvements |
|
||||
| `1.0` | `^7.2\|^8.0` | Initial version |
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"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": "^7.4 | ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0 | ^10.0 | ^11.0",
|
||||
"php-coveralls/php-coveralls": "^2.0",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" bootstrap="vendor/autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd">
|
||||
<testsuites>
|
||||
<testsuite name="Phrity Util/Accessor tests">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">./src/</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* File for ErrorHandler utility class.
|
||||
* @package Phrity > Util > ErrorHandler
|
||||
*/
|
||||
|
||||
namespace Phrity\Util;
|
||||
|
||||
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 mixed $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 Previously registered error handler, if any
|
||||
*/
|
||||
public function set($handling = null, int $levels = E_ALL)
|
||||
{
|
||||
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 mixed $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, $handling = null, int $levels = E_ALL)
|
||||
{
|
||||
$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 mixed $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, $handling = null, int $levels = E_ALL)
|
||||
{
|
||||
$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($handling)
|
||||
{
|
||||
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
|
||||
private function handle($handling, $error, $result = null)
|
||||
{
|
||||
if (is_callable($handling)) {
|
||||
return $handling($error, $result);
|
||||
}
|
||||
if (is_array($error)) {
|
||||
$error = array_shift($error);
|
||||
}
|
||||
if ($handling instanceof Throwable) {
|
||||
try {
|
||||
throw $error;
|
||||
} finally {
|
||||
throw $handling;
|
||||
}
|
||||
}
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* File for ErrorHandler function tests.
|
||||
* @package Phrity > Util > ErrorHandler
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phrity\Util;
|
||||
|
||||
use ErrorException;
|
||||
use RuntimeException;
|
||||
use Phrity\Util\ErrorHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* ErrorHandler test class.
|
||||
*/
|
||||
class ErrorHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Set up for all tests
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
error_reporting(-1);
|
||||
}
|
||||
|
||||
public function testSetNull(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$handler->set();
|
||||
|
||||
// Verify exception
|
||||
try {
|
||||
trigger_error('An error');
|
||||
} catch (ErrorException $e) {
|
||||
$this->assertEquals('An error', $e->getMessage());
|
||||
$this->assertEquals(0, $e->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $e->getSeverity());
|
||||
$this->assertNull($e->getPrevious());
|
||||
}
|
||||
|
||||
// Restore handler
|
||||
$this->assertTrue($handler->restore());
|
||||
}
|
||||
|
||||
public function testSetThrowable(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$handler->set(new RuntimeException('A provided exception', 23));
|
||||
|
||||
// Verify exception
|
||||
try {
|
||||
trigger_error('An error');
|
||||
} catch (RuntimeException $e) {
|
||||
$this->assertEquals('A provided exception', $e->getMessage());
|
||||
$this->assertEquals(23, $e->getCode());
|
||||
$this->assertNotNull($e->getPrevious());
|
||||
$prev = $e->getPrevious();
|
||||
$this->assertEquals('An error', $prev->getMessage());
|
||||
$this->assertEquals(0, $prev->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $prev->getSeverity());
|
||||
$this->assertNull($prev->getPrevious());
|
||||
}
|
||||
|
||||
// Restore handler
|
||||
$this->assertTrue($handler->restore());
|
||||
}
|
||||
|
||||
public function testSetCallback(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$result = null;
|
||||
$handler->set(function ($error) use (&$result) {
|
||||
$result = [
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'severity' => $error->getSeverity(),
|
||||
];
|
||||
});
|
||||
|
||||
// Verify exception
|
||||
trigger_error('An error');
|
||||
$this->assertEquals([
|
||||
'message' => 'An error',
|
||||
'code' => 0,
|
||||
'severity' => E_USER_NOTICE,
|
||||
], $result);
|
||||
|
||||
// Restore handler
|
||||
$this->assertTrue($handler->restore());
|
||||
}
|
||||
|
||||
public function testWithNull(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$check = false;
|
||||
|
||||
// No exception
|
||||
$result = $handler->with(function () {
|
||||
return 'Code success';
|
||||
});
|
||||
$this->assertEquals('Code success', $result);
|
||||
|
||||
// Verify exception
|
||||
try {
|
||||
$result = $handler->with(function () use (&$check) {
|
||||
trigger_error('An error');
|
||||
$check = true;
|
||||
return 'Code success';
|
||||
});
|
||||
} catch (ErrorException $e) {
|
||||
$this->assertEquals('An error', $e->getMessage());
|
||||
$this->assertEquals(0, $e->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $e->getSeverity());
|
||||
$this->assertNull($e->getPrevious());
|
||||
}
|
||||
$this->assertFalse($check);
|
||||
|
||||
// Verify that exception is thrown
|
||||
$this->expectException('ErrorException');
|
||||
$result = $handler->with(function () {
|
||||
trigger_error('An error');
|
||||
return 'Code success';
|
||||
});
|
||||
}
|
||||
|
||||
public function testWithThrowable(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$check = false;
|
||||
|
||||
// No exception
|
||||
$result = $handler->with(function () {
|
||||
return 'Code success';
|
||||
});
|
||||
$this->assertEquals('Code success', $result);
|
||||
|
||||
// Verify exception
|
||||
try {
|
||||
$result = $handler->with(function () use (&$check) {
|
||||
trigger_error('An error');
|
||||
$check = true;
|
||||
return 'Code success';
|
||||
}, new RuntimeException('A provided exception', 23));
|
||||
} catch (RuntimeException $e) {
|
||||
$this->assertEquals('A provided exception', $e->getMessage());
|
||||
$this->assertEquals(23, $e->getCode());
|
||||
$this->assertNotNull($e->getPrevious());
|
||||
$prev = $e->getPrevious();
|
||||
$this->assertEquals('An error', $prev->getMessage());
|
||||
$this->assertEquals(0, $prev->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $prev->getSeverity());
|
||||
$this->assertNull($prev->getPrevious());
|
||||
}
|
||||
$this->assertFalse($check);
|
||||
|
||||
// Verify that exception is thrown
|
||||
$this->expectException('RuntimeException');
|
||||
$result = $handler->with(function () {
|
||||
trigger_error('An error');
|
||||
return 'Code success';
|
||||
}, new RuntimeException('A provided exception', 23));
|
||||
}
|
||||
|
||||
public function testWithCallback(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$check = false;
|
||||
|
||||
// No error invoked
|
||||
$result = $handler->with(function () {
|
||||
return 'Code success';
|
||||
}, function ($error) {
|
||||
return $error;
|
||||
});
|
||||
$this->assertEquals('Code success', $result);
|
||||
|
||||
// An error is invoked
|
||||
$result = $handler->with(function () use (&$check) {
|
||||
trigger_error('An error');
|
||||
$check = true;
|
||||
return 'Code success';
|
||||
}, function ($error) {
|
||||
return $error;
|
||||
});
|
||||
$this->assertFalse($check);
|
||||
|
||||
$this->assertEquals('An error', $result->getMessage());
|
||||
$this->assertEquals(0, $result->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $result->getSeverity());
|
||||
$this->assertNull($result->getPrevious());
|
||||
}
|
||||
|
||||
public function testWithAllNull(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$check = false;
|
||||
|
||||
// No error invoked
|
||||
$result = $handler->withAll(function () {
|
||||
return 'Code success';
|
||||
});
|
||||
$this->assertEquals('Code success', $result);
|
||||
|
||||
// Verify exception
|
||||
try {
|
||||
$result = $handler->withAll(function () use (&$check) {
|
||||
trigger_error('An error');
|
||||
$check = true;
|
||||
return 'Code success';
|
||||
});
|
||||
} catch (ErrorException $e) {
|
||||
$this->assertEquals('An error', $e->getMessage());
|
||||
$this->assertEquals(0, $e->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $e->getSeverity());
|
||||
$this->assertNull($e->getPrevious());
|
||||
}
|
||||
$this->assertTrue($check);
|
||||
|
||||
// Verify that exception is thrown
|
||||
$this->expectException('ErrorException');
|
||||
$result = $handler->withAll(function () {
|
||||
trigger_error('An error');
|
||||
return 'Code success';
|
||||
});
|
||||
}
|
||||
|
||||
public function testWithAllThrowable(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$check = false;
|
||||
|
||||
// No exception
|
||||
$result = $handler->withAll(function () {
|
||||
return 'Code success';
|
||||
});
|
||||
$this->assertEquals('Code success', $result);
|
||||
|
||||
// Verify exception
|
||||
try {
|
||||
$result = $handler->withAll(function () use (&$check) {
|
||||
trigger_error('An error');
|
||||
$check = true;
|
||||
return 'Code success';
|
||||
}, new RuntimeException('A provided exception', 23));
|
||||
} catch (RuntimeException $e) {
|
||||
$this->assertEquals('A provided exception', $e->getMessage());
|
||||
$this->assertEquals(23, $e->getCode());
|
||||
$this->assertNotNull($e->getPrevious());
|
||||
$prev = $e->getPrevious();
|
||||
$this->assertEquals('An error', $prev->getMessage());
|
||||
$this->assertEquals(0, $prev->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $prev->getSeverity());
|
||||
$this->assertNull($prev->getPrevious());
|
||||
}
|
||||
$this->assertTrue($check);
|
||||
|
||||
// Verify that exception is thrown
|
||||
$this->expectException('RuntimeException');
|
||||
$result = $handler->withAll(function () {
|
||||
trigger_error('An error');
|
||||
return 'Code success';
|
||||
}, new RuntimeException('A provided exception', 23));
|
||||
}
|
||||
|
||||
public function testWithAllCallback(): void
|
||||
{
|
||||
$handler = new ErrorHandler();
|
||||
$check = false;
|
||||
|
||||
// No error invoked
|
||||
$result = $handler->withAll(function () {
|
||||
return 'Code success';
|
||||
}, function ($error, $result) {
|
||||
return $error;
|
||||
});
|
||||
$this->assertEquals('Code success', $result);
|
||||
|
||||
// An error is invoked
|
||||
$result = $handler->withAll(function () use (&$check) {
|
||||
trigger_error('An error');
|
||||
trigger_error('Another error', E_USER_WARNING);
|
||||
$check = true;
|
||||
return 'Code success';
|
||||
}, function ($errors, $result) {
|
||||
return ['errors' => $errors, 'result' => $result];
|
||||
});
|
||||
$this->assertTrue($check);
|
||||
|
||||
$this->assertEquals('Code success', $result['result']);
|
||||
$this->assertEquals('An error', $result['errors'][0]->getMessage());
|
||||
$this->assertEquals(0, $result['errors'][0]->getCode());
|
||||
$this->assertEquals(E_USER_NOTICE, $result['errors'][0]->getSeverity());
|
||||
$this->assertNull($result['errors'][0]->getPrevious());
|
||||
$this->assertEquals('Another error', $result['errors'][1]->getMessage());
|
||||
$this->assertEquals(0, $result['errors'][1]->getCode());
|
||||
$this->assertEquals(E_USER_WARNING, $result['errors'][1]->getSeverity());
|
||||
$this->assertNull($result['errors'][1]->getPrevious());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user