Browse Source

Initial import from Lax

master
J. King 4 years ago
commit
271fbaaaa3
  1. 7
      .gitattributes
  2. 5
      .gitignore
  3. 79
      .php_cs.dist
  4. 3
      AUTHORS
  5. 22
      LICENSE
  6. 146
      RoboFile.php
  7. 40
      composer.json
  8. 121
      composer.lock
  9. 235
      lib/AbstractUri.php
  10. 125
      lib/Uri.php
  11. 14
      robo
  12. 21
      robo.bat
  13. 20
      tests/bootstrap.php
  14. 476
      tests/cases/AbstractUriTestCase.php
  15. 38
      tests/cases/README
  16. 16
      tests/cases/UriTest.php
  17. 1877
      tests/cases/setters_tests.json
  18. 149
      tests/cases/toascii.json
  19. 6731
      tests/cases/urltestdata.json
  20. 26
      tests/phpunit.dist.xml
  21. 5
      vendor-bin/csfixer/composer.json
  22. 1541
      vendor-bin/csfixer/composer.lock
  23. 6
      vendor-bin/phpunit/composer.json
  24. 1754
      vendor-bin/phpunit/composer.lock
  25. 5
      vendor-bin/robo/composer.json
  26. 1529
      vendor-bin/robo/composer.lock

7
.gitattributes

@ -0,0 +1,7 @@
* text=auto encoding=utf-8
*.html diff=html
*.php diff=php
*.bat eol=crlf
*.cmd eol=crlf
.gitignore -eol

5
.gitignore

@ -0,0 +1,5 @@
/vendor
/vendor-bin/*/vendor
/tests/coverage
/tests/.phpunit.result.cache
/.php_cs.cache

79
.php_cs.dist

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
const BASE = __DIR__.DIRECTORY_SEPARATOR;
$paths = [
__FILE__,
BASE."RoboFile.php",
BASE."lib",
BASE."tests",
];
$rules = [
// house rules where PSR series is silent
'align_multiline_comment' => ['comment_type' => "phpdocs_only"],
'array_syntax' => ['syntax' => "short"],
'binary_operator_spaces' => [
'default' => "single_space",
'operators' => ['=>' => "align_single_space"],
],
'cast_spaces' => ['space' => "single"],
'concat_space' => ['spacing' => "none"],
'list_syntax' => ['syntax' => "short"],
'magic_constant_casing' => true,
'magic_method_casing' => true,
'modernize_types_casting' => true,
'native_function_casing' => true,
'native_function_type_declaration_casing' => true,
'no_binary_string' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_extra_blank_lines' => true, // this could probably use more configuration
'no_mixed_echo_print' => ['use' => "echo"],
'no_short_bool_cast' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unneeded_control_parentheses' => true,
'no_unneeded_curly_braces' => true,
'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
'normalize_index_brace' => true,
'object_operator_without_whitespace' => true,
'pow_to_exponentiation' => true,
'set_type_to_cast' => true,
'standardize_not_equals' => true,
'trailing_comma_in_multiline_array' => true,
'unary_operator_spaces' => true,
'yoda_style' => false,
// PSR standard to apply
'@PSR2' => true,
// PSR-12 rules; php-cs-fixer does not yet support PSR-12 natively
'compact_nullable_typehint' => true,
'declare_equal_normalize' => ['space' => "none"],
'function_typehint_space' => true,
'lowercase_cast' => true,
'lowercase_static_reference' => true,
'no_alternative_syntax' => true,
'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_whitespace_in_blank_line' => true,
'return_type_declaration' => ['space_before' => "none"],
'single_trait_insert_per_statement' => true,
'short_scalar_cast' => true,
'visibility_required' => ['elements' => ["const", "property", "method"]],
// house exceptions to PSR rules
'braces' => ['position_after_functions_and_oop_constructs' => "same"],
'function_declaration' => ['closure_function_spacing' => "none"],
'new_with_braces' => false, // no option to specify absence of braces
];
$finder = \PhpCsFixer\Finder::create();
foreach ($paths as $path) {
if (is_file($path)) {
$finder = $finder->append([$path]);
} else {
$finder = $finder->in($path);
}
}
return \PhpCsFixer\Config::create()->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);

3
AUTHORS

@ -0,0 +1,3 @@
Prohect lead
-------------
J. King https://jkingweb.ca/

22
LICENSE

@ -0,0 +1,22 @@
Copyright (c) 2020 J. King
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.

146
RoboFile.php

@ -0,0 +1,146 @@
<?php
use Robo\Result;
const BASE = __DIR__.\DIRECTORY_SEPARATOR;
const BASE_TEST = BASE."tests".\DIRECTORY_SEPARATOR;
define("IS_WIN", defined("PHP_WINDOWS_VERSION_MAJOR"));
define("IS_MAC", php_uname("s") === "Darwin");
function norm(string $path): string {
$out = realpath($path);
if (!$out) {
$out = str_replace(["/", "\\"], \DIRECTORY_SEPARATOR, $path);
}
return $out;
}
class RoboFile extends \Robo\Tasks {
/** Runs the typical test suite
*
* Arguments passed to the task are passed on to PHPUnit. Thus one may, for
* example, run the following command and get the expected results:
*
* ./robo test --testsuite TTRSS --exclude-group slow --testdox
*
* Please see the PHPUnit documentation for available options.
*/
public function test(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
}
/** Runs the full test suite
*
* This includes pedantic tests which may help to identify problems.
* See help for the "test" task for more details.
*/
public function testFull(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
}
/**
* Runs a quick subset of the test suite
*
* See help for the "test" task for more details.
*/
public function testQuick(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
}
/** Produces a code coverage report
*
* By default this task produces an HTML-format coverage report in
* tests/coverage/. Additional reports may be produced by passing
* arguments to this task as one would to PHPUnit.
*
* Robo first tries to use pcov and will fall back first to xdebug then
* phpdbg. Neither pcov nor xdebug need to be enabled to be used; they
* only need to be present in the extension load path to be used.
*/
public function coverage(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "coverage", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Produces a code coverage report, with redundant tests
*
* Depending on the environment, some tests that normally provide
* coverage may be skipped, while working alternatives are normally
* suppressed for reasons of time. This coverage report will try to
* run all tests which may cover code.
*
* See also help for the "coverage" task for more details.
*/
public function coverageFull(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "typical", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Runs the coding standards fixer */
public function clean($opts = ['demo|d' => false]): Result {
$t = $this->taskExec(norm(BASE."vendor/bin/php-cs-fixer"));
$t->arg("fix");
if ($opts['demo']) {
$t->args("--dry-run", "--diff")->option("--diff-format", "udiff");
}
return $t->run();
}
protected function findCoverageEngine(): string {
$dir = rtrim(ini_get("extension_dir"), "/").\DIRECTORY_SEPARATOR;
$ext = IS_WIN ? "dll" : (IS_MAC ? "dylib" : "so");
$php = escapeshellarg(\PHP_BINARY);
$code = escapeshellarg(BASE."lib");
if (extension_loaded("pcov")) {
return "$php -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (extension_loaded("xdebug")) {
return $php;
} elseif (file_exists($dir."pcov.$ext")) {
return "$php -d extension=pcov.$ext -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (file_exists($dir."pcov.$ext")) {
return "$php -d zend_extension=xdebug.$ext";
} else {
if (IS_WIN) {
$dbg = dirname(\PHP_BINARY)."\\phpdbg.exe";
$dbg = file_exists($dbg) ? $dbg : "";
} else {
$dbg = trim(`which phpdbg 2>/dev/null`);
}
if ($dbg) {
return escapeshellarg($dbg)." -qrr";
} else {
return $php;
}
}
}
protected function blackhole(bool $all = false): string {
$hole = IS_WIN ? "nul" : "/dev/null";
return $all ? ">$hole 2>&1" : "2>$hole";
}
protected function runTests(string $executor, string $set, array $args): Result {
switch ($set) {
case "typical":
$set = ["--exclude-group", "optional"];
break;
case "quick":
$set = ["--exclude-group", "optional,slow"];
break;
case "coverage":
$set = ["--exclude-group", "optional,coverageOptional"];
break;
case "full":
$set = [];
break;
default:
throw new \Exception;
}
$execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
$confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
//$this->taskServer(8000)->host("localhost")->dir(BASE_TEST."docroot")->rawArg("-n")->arg(BASE_TEST."server.php")->rawArg($this->blackhole())->background()->run();
return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
}
}

40
composer.json

@ -0,0 +1,40 @@
{
"name": "mensbeam/url",
"type": "library",
"description": "An implementation of PSR-7 URI interface and the WHATWG URL specification",
"keywords": ["whatwg", "url", "psr7"],
"license": "MIT",
"authors": [
{
"name": "J. King",
"email": "jking@jkingweb.ca",
"homepage": "https://jkingweb.ca/"
}
],
"require": {
"php": "^7.1",
"psr/http-message": "^1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.3"
},
"config": {
"platform": {
"php": "7.1.33"
}
},
"scripts": {
"post-install-cmd": ["@composer bin all install"],
"post-update-cmd": ["@composer bin all update"]
},
"autoload": {
"psr-4": {
"MensBeam\\Url\\": "lib/"
}
},
"autoload-dev": {
"psr-4": {
"MensBeam\\Url\\TestCase\\": "tests/cases/"
}
}
}

121
composer.lock

@ -0,0 +1,121 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cc13358eca8e6572126bb5ac30e285e4",
"packages": [
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
}
],
"packages-dev": [
{
"name": "bamarni/composer-bin-plugin",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/bamarni/composer-bin-plugin.git",
"reference": "46cb272590cc6b7f5947655063a7fd6ea097838b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/46cb272590cc6b7f5947655063a7fd6ea097838b",
"reference": "46cb272590cc6b7f5947655063a7fd6ea097838b",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0",
"php": "^5.6 || ^7.0 || ^8.0"
},
"require-dev": {
"composer/composer": "^1.0 || ^2.0",
"symfony/console": "^2.5 || ^3.0 || ^4.0"
},
"type": "composer-plugin",
"extra": {
"class": "Bamarni\\Composer\\Bin\\Plugin"
},
"autoload": {
"psr-4": {
"Bamarni\\Composer\\Bin\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "No conflicts for your bin dependencies",
"keywords": [
"composer",
"conflict",
"dependency",
"executable",
"isolation",
"tool"
],
"time": "2020-04-17T09:33:47+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.1"
},
"platform-dev": [],
"platform-overrides": {
"php": "7.1.33"
},
"plugin-api-version": "1.1.0"
}

235
lib/AbstractUri.php

@ -0,0 +1,235 @@
<?php
/** @license MIT
* Copyright 2020 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\Url;
abstract class AbstractUri {
protected const URI_PATTERN = <<<'PCRE'
<^
(?:
(?:
([a-z][a-z0-9\.\-\+]*): | # absolute URI
:?(?=//) # scheme-relative URI
)
(//[^/\?\#]*)? # authority part
)?
([^\?\#]*) # path part
(\?[^\#]*)? # query part
(\#.*)? # fragment part
$>six
PCRE;
protected const AUTHORITY_PATTERN = <<<'PCRE'
<^
//
(?:
([^@:]*) # username part
(?::([^@]*))? # password part
@
)?
(
\[[a-f0-9:]*\] | # IPv6 address
[^:]+ # domain or IPv4 address
)
(?:
:(\d*) # port part
)?
$>six
PCRE;
protected const SCHEME_PATTERN = "<^(?:[a-z][a-z0-9\.\-\+]*|)$>i";
protected const IPV6_PATTERN = "<^\[[a-f0-9:]+\]$>i";
protected const PORT_PATTERN = "<^\d*$>";
protected const ESCAPE_CHARS = [
'user' => [":", "@", "/", "?", "#"],
'pass' => ["@", "/", "?", "#"],
'path' => ["?", "#"],
'query' => ["#"],
];
protected $scheme = null;
protected $user = "";
protected $pass = "";
protected $host = null;
protected $port = null;
protected $path = null;
protected $query = null;
protected $fragment = null;
public function __construct(string $url, string $baseUrl = null) {
if (preg_match(self::URI_PATTERN, $url, $match)) {
[$url, $scheme, $authority, $path, $query, $fragment] = array_pad($match, 6, "");
foreach (["scheme", "path", "query", "fragment"] as $part) {
if (strlen($$part)) {
if ($part === "query" || $part === "fragment") {
$$part = substr($$part, 1);
}
$this->set($part, $$part);
}
}
if (strlen($authority)) {
if (preg_match(self::AUTHORITY_PATTERN, $authority, $match)) {
[$authority, $user, $pass, $host, $port] = array_pad($match, 5, "");
foreach (["user", "pass", "host", "port"] as $part) {
$this->set($part, $$part);
}
}
}
if ($baseUrl && !$this->scheme) {
$this->resolve(new static($baseUrl));
}
foreach (["scheme", "path", "query", "fragment"] as $part) {
$this->$part = $this->$part ?? "";
}
} else {
throw new \InvalidArgumentException("String is not a valid URI");
}
}
public function __toString() {
$out = "";
$out .= strlen($this->scheme) ? $this->scheme.":" : "";
if (is_null($this->host)) {
$out .= $this->path;
} else {
$auth = "";
if (strlen($this->host ?? "") > 0) {
if (strlen($this->user ?? "")) {
$auth .= $this->user.(strlen($this->pass ?? "") ? ":".$this->pass : "")."@";
}
$auth .= $this->host;
$auth .= !is_null($this->port) ? ":".$this->port : "";
}
$out .= "//$auth";
$out .= ($this->path[0] ?? "") === "/" ? "" : "/";
$out .= preg_replace("<^/{2,}/>", "/", $this->path);
}
$out .= strlen($this->query) ? "?".$this->query : "";
$out .= strlen($this->fragment) ? "#".$this->fragment : "";
return $out;
}
protected function set(string $name, $value): void {
switch ($name) {
case "host":
$this->host = $this->normalizeHost($value);
break;
case "port":
if (preg_match(self::PORT_PATTERN, (string) $value, $match)) {
$this->port = strlen($match[0]) ? (int) $value : null;
} else {
throw new \InvalidArgumentException("Port must be an integer or null");
}
break;
case "scheme":
if (preg_match(self::SCHEME_PATTERN, $value)) {
$this->scheme = strtolower($value);
} else {
throw new \InvalidArgumentException("Invalid scheme specified");
}
break;
default:
$this->$name = $this->normalizeEncoding((string) $value, $name);
}
}
protected function resolve(self $base): void {
[$scheme, $host, $user, $pass, $port, $path, $query, $fragment] = [$base->scheme, $base->host, $base->user, $base->pass, $base->port, $base->path, $base->query, $base->fragment];
if (strlen($scheme) && is_null($host)) {
throw new \InvalidArgumentException("URL base must not be a Uniform Resource Name");
}
$this->scheme = $this->scheme ?? $scheme;
if (is_null($this->host)) {
$this->host = $host;
$this->user = $user;
$this->pass = $pass;
$this->port = $port;
if (is_null($this->path)) {
$this->path = $path;
if (is_null($this->query)) {
$this->query = $query;
if (is_null($this->fragment)) {
$this->fragment = $fragment;
}
}
} elseif (strlen($path)) {
if ($this->path[0] !== "/") {
if ($path[-1] === "/") {
$this->path = $path.$this->path;
} else {
$this->path = substr($path, 0, (int) strrpos($path, "/")).$this->path;
}
}
}
}
}
protected function normalizeEncoding(string $data, string $part = null): string {
$pos = 0;
$end = strlen($data);
$out = "";
$esc = self::ESCAPE_CHARS[$part] ?? [];
// process each character in sequence
while ($pos < $end) {
$c = $data[$pos];
if ($c === "%") {
// the % character signals an encoded character...
$d = substr($data, $pos + 1, 2);
if (!preg_match("/^[0-9a-fA-F]{2}$/", $d)) {
// unless there are fewer than two characters left in the string or the two characters are not hex digits
$d = ord($c);
} else {
$d = hexdec($d);
$pos += 2;
}
} else {
$d = ord($c);
}
$dc = chr($d);
if ($d < 0x21 || $d > 0x7E || $d == 0x25) {
// these characters are always encoded
$out .= "%".strtoupper(dechex($d));
} elseif (preg_match("/[a-zA-Z0-9\._~-]/", $dc)) {
// these characters are never encoded
$out .= $dc;
} else {
// these characters are passed through as-is...
if ($c === "%") {
$out .= "%".strtoupper(dechex($d));
} else {
// unless the part we're processing has delimiters which must be escaped
if (in_array($dc, $esc)) {
$out .= "%".strtoupper(dechex($d));
} else {
$out .= $c;
}
}
}
$pos++;
}
return $out;
}
/** Normalizes a hostname per IDNA:2008 */
protected function normalizeHost(?string $host): ?string {
if (!is_null($host) && strlen($host)) {
if (preg_match(self::IPV6_PATTERN, $host)) {
// normalize IPv6 addresses
$addr = @inet_pton(substr($host, 1, strlen($host) - 2));
if ($addr !== false) {
return "[".inet_ntop($addr)."]";
}
}
$idn = idn_to_ascii($host, \IDNA_NONTRANSITIONAL_TO_ASCII | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ, \INTL_IDNA_VARIANT_UTS46);
if ($idn === false) {
throw new \InvalidArgumentException("Invalid host in URL");
}
$host = idn_to_utf8($idn, \IDNA_NONTRANSITIONAL_TO_UNICODE | \IDNA_USE_STD3_RULES, \INTL_IDNA_VARIANT_UTS46);
if ($host === false) {
throw new \InvalidArgumentException("Invalid host in URL");
}
}
return $host;
}
}

125
lib/Uri.php

@ -0,0 +1,125 @@
<?php
/** @license MIT
* Copyright 2020 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\Url;
use Psr\Http\Message\UriInterface;
/** Normalized URI representation, compatible with the PSR-7 URI interface
*
* The following features are implemented:
*
* - The full PSR-7 `UriInterface` interface
* - Correct handling of both URLs and URNs
* - Relative URL resolution
* - Encoding normalization
* - Scheme normalization
* - IDNA normalization
* - IPv6 address normalization
* - Empty query and fragment removal
*
* Some things this class does not do:
*
* - Handle non-standard schemes (e.g. ed2k)
* - Collapse paths
* - Drop default ports
*
* This class should not be used with XML namespace URIs,
* as the normalizations performed will change the values
* of some namespaces.
*/
class Uri extends AbstractUri implements UriInterface {
public function getAuthority() {
$host = $this->getHost();
if (strlen($host) > 0) {
$userInfo = $this->getUserInfo();
$port = $this->getPort();
return (strlen($userInfo) ? $userInfo."@" : "").$host.(!is_null($port) ? ":".$port : "");
}
return "";
}
public function getFragment() {
return $this->fragment ?? "";
}
public function getHost() {
return $this->host ?? "";
}
public function getPath() {
return $this->path ?? "";
}
public function getPort() {
return $this->port;
}
public function getQuery() {
return $this->query ?? "";
}
public function getScheme() {
return $this->scheme ?? "";
}
public function getUserInfo() {
if (strlen($this->user ?? "")) {
return $this->user.(strlen($this->pass ?? "") ? ":".$this->pass : "");
}
return "";
}
public function withFragment($fragment) {
$out = clone $this;
$out->set("fragment", $fragment);
return $out;
}
public function withHost($host) {
if ($host === "") {
$host = null;
}
$out = clone $this;
$out->set("host", $host);
return $out;
}
public function withPath($path) {
$out = clone $this;
$out->set("path", $path);
return $out;
}
public function withPort($port) {
$out = clone $this;
$out->set("port", $port);
return $out;
}
public function withQuery($query) {
$out = clone $this;
$out->set("query", $query);
return $out;
}
public function withScheme($scheme) {
$out = clone $this;
$out->set("scheme", $scheme);
return $out;
}
public function withUserInfo($user, $password = null) {
$out = clone $this;
$out->set("user", $user);
$out->set("pass", $password);
return $out;
}
public function __get(string $name) {
return $this->$name;
}
}

14
robo

@ -0,0 +1,14 @@
#! /bin/sh
base=`dirname "$0"`
roboCommand="$1"
if [ $# -eq 0 ]; then
"$base/vendor/bin/robo"
else
shift
ulimit -n 2048
if [ "$1" = "clean" ]; then
"$base/vendor/bin/robo" "$roboCommand" "$@"
else
"$base/vendor/bin/robo" "$roboCommand" -- "$@"
fi
fi

21
robo.bat

@ -0,0 +1,21 @@
@echo off
setlocal
set base=%~dp0
set roboCommand=%1
rem get all arguments except the first
shift
set "args="
:parse
if "%~1" neq "" (
set args=%args% %1
shift
goto :parse
)
if defined args set args=%args:~1%
if "%1"=="clean" (
call "%base%vendor\bin\robo" "%roboCommand%" %args%
) else (
call "%base%vendor\bin\robo" "%roboCommand%" -- %args%
)

20
tests/bootstrap.php

@ -0,0 +1,20 @@
<?php
/** @license MIT
* Copyright 2020 J. King
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\Mime;
const NS_BASE = __NAMESPACE__."\\";
define(NS_BASE."BASE", dirname(__DIR__).DIRECTORY_SEPARATOR);
const DOCROOT = BASE."tests".DIRECTORY_SEPARATOR."docroot".DIRECTORY_SEPARATOR;
ini_set("memory_limit", "-1");
ini_set("zend.assertions", "1");
ini_set("assert.exception", "true");
error_reporting(\E_ALL);
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
if (function_exists("xdebug_set_filter")) {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [BASE."lib/"]);
}

476
tests/cases/AbstractUriTestCase.php

@ -0,0 +1,476 @@
<?php
namespace MensBeam\Url\TestCase;
use Psr\Http\Message\UriInterface;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
abstract class AbstractUriTestCase extends TestCase {
/**
* @var UriInterface
*/
protected $uri;
protected $uri_string = 'http://login:pass@secure.example.com:443/test/query.php?kingkong=toto#doc3';
/**
* UriInterface factory
*
* @param string $uri
*
* @return UriInterface
*/
abstract protected function createUri($uri = '');
protected function setUp(): void {
$this->uri = $this->createUri($this->uri_string);
}
protected function tearDown(): void {
$this->uri = null;
}
/**
* @group scheme
* @dataProvider schemeProvider
*
* The value returned MUST be normalized to lowercase, per RFC 3986
* Section 3.1.
*/
public function testGetScheme($scheme, $expected) {
$uri = $this->uri->withScheme($scheme);
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getScheme(), 'Scheme must be normalized according to RFC3986');
}
public function schemeProvider() {
return [
'normalized scheme' => ['HtTpS', 'https'],
'simple scheme' => ['http', 'http'],
'no scheme' => ['', ''],
];
}
/**
* @group userinfo
* @dataProvider userInfoProvider
*
* If a user is present in the URI, this will return that value;
* additionally, if the password is also present, it will be appended to the
* user value, with a colon (":") separating the values.
*
*/
public function testGetUserInfo($user, $pass, $expected) {
$uri = $this->uri->withUserInfo($user, $pass);
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getUserInfo(), 'UserInfo must be normalized according to RFC3986');
}
public function userInfoProvider() {
return [
'with userinfo' => ['iGoR', 'rAsMuZeN', 'iGoR:rAsMuZeN'],
'no userinfo' => ['', '', ''],
'no pass' => ['iGoR', '', 'iGoR'],
'pass is null' => ['iGoR', null, 'iGoR'],
'case sensitive' => ['IgOr', 'RaSm0537', 'IgOr:RaSm0537'],
];
}
/**
* @group host
* @dataProvider hostProvider
*
* The value returned MUST be normalized to lowercase, per RFC 3986
* Section 3.2.2.
*
*/
public function testGetHost($host, $expected) {
$uri = $this->uri->withHost($host);
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getHost(), 'Host must be normalized according to RFC3986');
}
public function hostProvider() {
return [
'normalized host' => ["MaStEr.eXaMpLe.CoM", "master.example.com"],
"simple host" => ["www.example.com", "www.example.com"],
"IPv6 Host" => ["[::1]", "[::1]"],
];
}
/**
* @group port
* @dataProvider portProvider
*
* If a port is present, and it is non-standard for the current scheme,
* this method MUST return it as an integer. If the port is the standard port
* used with the current scheme, this method SHOULD return null.
*
* If no port is present, and no scheme is present, this method MUST return
* a null value.
*
* If no port is present, but a scheme is present, this method MAY return
* the standard port for that scheme, but SHOULD return null.
*/
public function testPort($uri, $port, $expected) {
$uri = $this->createUri($uri)->withPort($port);
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getPort(), 'port must be an int or null');
}
public function portProvider() {
return [
'non standard port for http' => ['http://www.example.com', 443, 443],
'remove port' => ['http://www.example.com', null, null],
'standard port on schemeless http url' => ['//www.example.com', 80, 80],
];
}
/**
* @group port
*/
public function testUriWithStandardPort() {
$uri = $this->createUri('http://example.com:80');
$this->assertContains($uri->getPort(), [80, null], "If no port is present, but a scheme is present, this method MAY return the standard port for that scheme, but SHOULD return null.");
}
/**
* @group authority
* @dataProvider authorityProvider
*
* If the port component is not set or is the standard port for the current
* scheme, it SHOULD NOT be included.
*/
public function testGetAuthority($scheme, $user, $pass, $host, $port, $authority) {
$uri = $this
->createUri()
->withHost($host)
->withScheme($scheme)
->withUserInfo($user, $pass)
->withPort($port)
;
$this->assertSame($authority, $uri->getAuthority());
}
public function authorityProvider() {
return [
'authority' => [
'scheme' => 'http',
'user' => 'iGoR',
'pass' => 'rAsMuZeN',
'host' => 'master.example.com',
'port' => 443,
'authority' => 'iGoR:rAsMuZeN@master.example.com:443',
],
'without port' => [
'scheme' => 'http',
'user' => 'iGoR',
'pass' => 'rAsMuZeN',
'host' => 'master.example.com',
'port' => null,
'authority' => 'iGoR:rAsMuZeN@master.example.com',
],
'with standard port' => [
'scheme' => 'http',
'user' => 'iGoR',
'pass' => 'rAsMuZeN',
'host' => 'master.example.com',
'port' => 80,
'authority' => 'iGoR:rAsMuZeN@master.example.com:80',
],
"authority without pass" => [
'scheme' => 'http',
'user' => 'iGoR',
'pass' => '',
'host' => 'master.example.com',
'port' => null,
'authority' => 'iGoR@master.example.com',
],
"authority without port and userinfo" => [
'scheme' => 'http',
'user' => '',
'pass' => '',
'host' => 'master.example.com',
'port' => null,
'authority' => 'master.example.com',
],
];
}
/**
* @group query
* @dataProvider queryProvider
*
* The value returned MUST be percent-encoded, but MUST NOT double-encode
* any characters. To determine what characters to encode, please refer to
* RFC 3986, Sections 2 and 3.4.
*/
public function testGetQuery($query, $expected) {
$uri = $this->uri->withQuery($query);
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getQuery(), 'Query must be normalized according to RFC3986');
}
public function queryProvider() {
return [
'normalized query' => ['foo.bar=%7evalue', 'foo.bar=~value'],
'empty query' => ['', ''],
'same param query' => ['foo.bar=1&foo.bar=1', 'foo.bar=1&foo.bar=1'],
'same param query' => ['?foo=1', '?foo=1'],
];
}
/**
* @group fragment
* @dataProvider fragmentProvider
*
* The value returned MUST be percent-encoded, but MUST NOT double-encode
* any characters. To determine what characters to encode, please refer to
* RFC 3986, Sections 2 and 3.5.
*/
public function testGetFragment($fragment, $expected) {
$uri = $this->uri->withFragment($fragment);
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getFragment(), 'Fragment must be normalized according to RFC3986');
}
public function fragmentProvider() {
return [
'URL with full components' => ['fragment', 'fragment'],
'URL with non-encodable fragment' => ["azAZ0-9/?-._~!$&'()*+,;=:@", "azAZ0-9/?-._~!$&'()*+,;=:@"],
];
}
/**
* @group uri
* @dataProvider stringProvider
*
* - If a scheme is present, it MUST be suffixed by ":".
* - If an authority is present, it MUST be prefixed by "//".
* - The path can be concatenated without delimiters. But there are two
* cases where the path has to be adjusted to make the URI reference
* valid as PHP does not allow to throw an exception in __toString():
* - If the path is rootless and an authority is present, the path MUST
* be prefixed by "/".
* - If the path is starting with more than one "/" and no authority is
* present, the starting slashes MUST be reduced to one.
* - If a query is present, it MUST be prefixed by "?".
* - If a fragment is present, it MUST be prefixed by "#".
*/
public function testToString($scheme, $user, $pass, $host, $port, $path, $query, $fragment, $expected) {
$uri = $this->createUri()
->withHost($host)
->withScheme($scheme)
->withUserInfo($user, $pass)
->withPort($port)
->withPath($path)
->withQuery($query)
->withFragment($fragment)
;
$this->assertSame(
$expected,
(string) $uri,
'URI string must be normalized according to RFC3986 rules'
);
}
public function stringProvider() {
return [
'URL normalized' => [
'scheme' => 'HtTps',
'user' => 'iGoR',
'pass' => 'rAsMuZeN',
'host' => 'MaStEr.eXaMpLe.CoM',
'port' => 443,
'path' => '/%7ejohndoe/%a1/index.php',
'query' => 'foo.bar=%7evalue',
'fragment' => 'fragment',
'uri' => 'https://iGoR:rAsMuZeN@master.example.com:443/~johndoe/%A1/index.php?foo.bar=~value#fragment',
],
'URL without scheme' => [
'scheme' => '',
'user' => '',
'pass' => '',
'host' => 'www.example.com',
'port' => 443,
'path' => '/foo/bar',
'query' => 'param=value',
'fragment' => 'fragment',
'uri' => '//www.example.com:443/foo/bar?param=value#fragment',
],
'URL without authority and scheme' => [
'scheme' => '',
'user' => '',
'pass' => '',
'host' => '',
'port' => null,
'path' => 'foo/bar',
'query' => '',
'fragment' => '',
'uri' => 'foo/bar',
],
];
}
/**
* @group fragment
*/
public function testRemoveFragment() {
$uri = 'http://example.com/path/to/me';
$this->assertSame($uri, (string) $this->createUri($uri.'#doc')->withFragment(''));
}
/**
* @group query
*/
public function testRemoveQuery() {
$uri = 'http://example.com/path/to/me';
$this->assertSame($uri, (string) (string) $this->createUri($uri.'?name=value')->withQuery(''));
}
/**
* @group path
*/
public function testRemovePath() {
$uri = 'http://example.com';
$this->assertContains(
(string) $this->createUri($uri.'/path/to/me')->withPath(''),
[$uri, $uri.'/']
);
}
/**
* @group port
*/
public function testRemovePort() {
$this->assertSame(
'http://example.com/path/to/me',
(string) $this->createUri('http://example.com:81/path/to/me')->withPort(null)
);
}
/**
* @group userinfo
*/
public function testRemoveUserInfo() {
$this->assertSame(
'http://example.com/path/to/me',
(string) $this->createUri('http://user:pass@example.com/path/to/me')->withUserInfo('')
);
}
/**
* @group scheme
*/
public function testRemoveScheme() {
$this->assertSame(
'//example.com/path/to/me',
(string) $this->createUri('http://example.com/path/to/me')->withScheme('')
);
}
/**
* @group authority
*/
public function testRemoveAuthority() {
$uri = 'http://user:login@example.com:82/path?q=v#doc';
$uri_with_host = $this->createUri($uri)
->withScheme('')
->withUserInfo('')
->withPort(null)
->withHost('')
;
$this->assertSame('/path?q=v#doc', (string) $uri_with_host);
}
/**
* @group scheme
* @dataProvider withSchemeFailedProvider
*/
public function testWithSchemeFailed($scheme) {
$this->expectException(InvalidArgumentException::class);
$this->uri->withScheme($scheme);
}
public function withSchemeFailedProvider() {
return [
'invalid char' => ['in,valid'],
'integer like string' => ['123'],
];
}
/**
* @group host
* @dataProvider withHostFailedProvider
*/
public function testWithHostFailed($host) {
$this->expectException(InvalidArgumentException::class);
$this->uri->withHost($host);
}
public function withHostFailedProvider() {
return [
'dot in front' => ['.example.com'],
'hyphen suffix' => ['host.com-'],
'multiple dot' => ['.......'],
'one dot' => ['.'],
'empty label' => ['tot. .coucou.com'],
'space in the label' => ['re view'],
'underscore in label' => ['_bad.host.com'],
'label too long' => [implode('', array_fill(0, 12, 'banana')).'.secure.example.com'],
'too many labels' => [implode('.', array_fill(0, 128, 'a'))],
'Invalid IPv4 format' => ['[127.0.0.1]'],
'Invalid IPv6 format' => ['[[::1]]'],
'Invalid IPv6 format 2' => ['[::1'],
'space character in starting label' => ['example. com'],
'invalid character in host label' => ["examp\0le.com"],
'invalid IP with scope' => ['[127.2.0.1%253]'],
'invalid scope IPv6' => ['ab23::1234%251'],
'invalid scope ID' => ['fe80::1234%25?@'],
'invalid scope ID with utf8 character' => ['fe80::1234%25€'],
];
}
/**
* @group host
*/
public function testModificationFailedWithInvalidHost() {
$this->expectException(InvalidArgumentException::class);
$this->createUri('http://example.com')->withHost('?');
}
/**
* @group uri
* @dataProvider invalidURI
*/
public function testCreateFromInvalidUrlKO($uri) {
$this->expectException(InvalidArgumentException::class);
$this->createUri($uri);
}
public function invalidURI() {
return [
['http://user@:80'],
];
}
/**
* @group uri
*/
public function testEmptyValueDetection() {
$expected = '//0:0@0/0?0#0';
$this->assertSame($expected, (string) $this->createUri($expected));
}
/**
* @group path
* @group uri
*/
public function testPathDetection() {
$expected = 'foo/bar:';
$this->assertSame($expected, $this->createUri($expected)->getPath());
}
}

38
tests/cases/README

@ -0,0 +1,38 @@
The Url class used in Lax is an original work, but its test suite is largely
composed of existing test corpuses from the following sources:
1. The Web Platform Test suite
<https://github.com/web-platform-tests/wpt/tree/1c72611f0020534154146177001ff8758de254fd/url/resources>
2. Bakame PSR-7 UriInterface test suite
<https://github.com/bakame-php/psr7-uri-interface-tests/tree/5a556fdfe668a6c6a14772efeba6134c0b7dae34>
Their license texts are reproduced below.
Copyright 2019 web-platform-tests contributors
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (c) 2015 ignace nyamagana butera
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

16
tests/cases/UriTest.php

@ -0,0 +1,16 @@
<?php
/** @license MIT
* Copyright 2020 J. King
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\Url\TestCase;
use MensBeam\Url\Uri;
/** @covers MensBeam\Url\Uri<extended> */
class UrlTest extends AbstractUriTestCase {
protected function createUri($uri = '') {
return new Uri($uri);
}
}

1877
tests/cases/setters_tests.json

File diff suppressed because it is too large

149
tests/cases/toascii.json

@ -0,0 +1,149 @@
[
"This resource is focused on highlighting issues with UTS #46 ToASCII",
{
"comment": "Label with hyphens in 3rd and 4th position",
"input": "aa--",
"output": "aa--"
},
{
"input": "a†--",
"output": "xn--a---kp0a"
},
{
"input": "ab--c",
"output": "ab--c"
},
{
"comment": "Label with leading hyphen",
"input": "-x",
"output": "-x"
},
{
"input": "-†",
"output": "xn----xhn"
},
{
"input": "-x.xn--nxa",
"output": "-x.xn--nxa"
},
{
"input": "-x.β",
"output": "-x.xn--nxa"
},
{
"comment": "Label with trailing hyphen",
"input": "x-.xn--nxa",
"output": "x-.xn--nxa"
},
{
"input": "x-.β",
"output": "x-.xn--nxa"
},
{
"comment": "Empty labels",
"input": "x..xn--nxa",
"output": "x..xn--nxa"
},
{
"input": "x..β",
"output": "x..xn--nxa"
},
{
"comment": "Invalid Punycode",
"input": "xn--a",
"output": null
},
{
"input": "xn--a.xn--nxa",
"output": null
},
{
"input": "xn--a.β",
"output": null
},
{
"comment": "Valid Punycode",
"input": "xn--nxa.xn--nxa",
"output": "xn--nxa.xn--nxa"
},
{
"comment": "Mixed",
"input": "xn--nxa.β",
"output": "xn--nxa.xn--nxa"
},
{
"input": "ab--c.xn--nxa",
"output": "ab--c.xn--nxa"
},
{
"input": "ab--c.β",
"output": "ab--c.xn--nxa"
},
{
"comment": "CheckJoiners is true",
"input": "\u200D.example",
"output": null
},
{
"input": "xn--1ug.example",
"output": null
},
{
"comment": "CheckBidi is true",
"input": "يa",
"output": null
},
{
"input": "xn--a-yoc",
"output": null
},
{
"comment": "processing_option is Nontransitional_Processing",
"input": "ශ්‍රී",
"output": "xn--10cl1a0b660p"
},
{
"input": "نامه‌ای",
"output": "xn--mgba3gch31f060k"
},
{
"comment": "U+FFFD",
"input": "\uFFFD.com",
"output": null
},
{
"comment": "U+FFFD character encoded in Punycode",
"input": "xn--zn7c.com",
"output": null
},
{
"comment": "Label longer than 63 code points",
"input": "x01234567890123456789012345678901234567890123456789012345678901x",
"output": "x01234567890123456789012345678901234567890123456789012345678901x"
},
{
"input": "x01234567890123456789012345678901234567890123456789012345678901†",
"output": "xn--x01234567890123456789012345678901234567890123456789012345678901-6963b"
},
{
"input": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa",
"output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa"
},
{
"input": "x01234567890123456789012345678901234567890123456789012345678901x.β",
"output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa"
},
{
"comment": "Domain excluding TLD longer than 253 code points",
"input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x",
"output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x"
},
{
"input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa",
"output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa"
},
{
"input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.β",
"output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa"
}
]

6731
tests/cases/urltestdata.json

File diff suppressed because it is too large

26
tests/phpunit.dist.xml

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<phpunit
colors="true"
bootstrap="bootstrap.php"
cacheTokens="true"
convertErrorsToExceptions="false"
convertNoticesToExceptions="false"
convertWarningsToExceptions="false"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
forceCoversAnnotation="true"
executionOrder="default"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../lib</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="PSR-7">
<file>cases/UriTest.php</file>
</testsuite>
</testsuites>
</phpunit>

5
vendor-bin/csfixer/composer.json

@ -0,0 +1,5 @@
{
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16"
}
}

1541
vendor-bin/csfixer/composer.lock

File diff suppressed because it is too large

6
vendor-bin/phpunit/composer.json

@ -0,0 +1,6 @@
{
"require-dev": {
"phpunit/phpunit": "^9.0",
"symfony/yaml": "^5.0"
}
}

1754
vendor-bin/phpunit/composer.lock

File diff suppressed because it is too large

5
vendor-bin/robo/composer.json

@ -0,0 +1,5 @@
{
"require-dev": {
"consolidation/robo": "^2.0"
}
}

1529
vendor-bin/robo/composer.lock

File diff suppressed because it is too large