diff --git a/.editorconfig b/.editorconfig index cd8eb86..a7c44dd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,3 @@ -; This file is for unifying the coding style for different editors and IDEs. -; More information at http://editorconfig.org - root = true [*] @@ -13,3 +10,6 @@ trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes index 84f5da8..1adc5e2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,9 +2,11 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.github export-ignore /.gitattributes export-ignore /.gitignore export-ignore /phpunit.xml.dist export-ignore -/.scrutinizer.yml export-ignore /tests export-ignore +/.editorconfig export-ignore +/.php_cs export-ignore +/.github export-ignore +/psalm.xml export-ignore diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..fe5143b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: spatie +custom: https://spatie.be/open-source/support-us diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..ca91343 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker. diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml new file mode 100644 index 0000000..4cf285f --- /dev/null +++ b/.github/workflows/php-cs-fixer.yml @@ -0,0 +1,23 @@ +name: Check & fix styling + +on: [push] + +jobs: + php-cs-fixer: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Run PHP CS Fixer + uses: docker://oskarstark/php-cs-fixer-ga + with: + args: --config=.php_cs.dist --allow-risky=yes + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Fix styling diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 94d2941..12fc7f7 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,40 +1,41 @@ -name: run-tests +name: Tests on: - push: - pull_request: - schedule: - - cron: '0 0 * * *' + push: + pull_request: + schedule: + - cron: '0 0 * * *' jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - php: [8.0, 7.4, 7.3, 7.2] - dependency-version: [prefer-lowest, prefer-stable] - os: [ubuntu-latest, windows-latest] - - name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: json, dom, curl, libxml, mbstring - coverage: none - - - name: Install dependencies - run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest - - - name: Execute tests on Ubuntu OS - if: matrix.operating-system == 'ubuntu-latest' - run: XDEBUG_MODE=coverage vendor/bin/phpunit - - - name: Execute tests on Windows OS - if: matrix.operating-system == 'windows-latest' - run: vendor/bin/phpunit + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, windows-latest] + php: [8.0] + stability: [prefer-lowest, prefer-stable] + + name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, json, libxml, mbstring + coverage: none + + - name: Setup problem matchers + run: | + echo "::add-matcher::${{ runner.tool_cache }}/php.json" + echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Install dependencies + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction + + - name: Execute tests + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index b6285d6..0296b57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ +.idea +.php_cs +.php_cs.cache +.phpunit.result.cache build composer.lock +coverage docs +phpunit.xml +psalm.xml vendor -.phpunit.result.cache diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..ac127a7 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,40 @@ +in([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->name('*.php') + ->notName('*.blade.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_imports' => ['sortAlgorithm' => 'alpha'], + 'no_unused_imports' => true, + 'not_operator_with_successor_space' => true, + 'trailing_comma_in_multiline_array' => true, + 'phpdoc_scalar' => true, + 'unary_operator_spaces' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_statement' => [ + 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], + ], + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_var_without_name' => true, + 'class_attributes_separation' => [ + 'elements' => [ + 'method', + ], + ], + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + 'keep_multiple_spaces_after_comma' => true, + ], + 'single_trait_insert_per_statement' => true, + ]) + ->setFinder($finder); diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 0285f17..0000000 --- a/.styleci.yml +++ /dev/null @@ -1 +0,0 @@ -preset: laravel diff --git a/CHANGELOG.md b/CHANGELOG.md index 48113fa..906c903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All Notable changes to `url` will be documented in this file +## 2.0.0 - unreleased + +- require PHP 8+ +- drop support for PHP 7.x +- use PHP 8 syntax where possible + ## 1.3.5 - 2020-11-05 - update deps diff --git a/README.md b/README.md index 50849e3..89c16ad 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Parse, build and manipulate URL's +# Parse, build and manipulate URLs [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/url.svg?style=flat-square)](https://packagist.org/packages/spatie/url) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) -![run-tests](https://github.com/spatie/url/workflows/run-tests/badge.svg) +[![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/spatie/url/Tests?label=tests)](https://github.com/spatie/url/actions?query=workflow%3ATests+branch%3Amaster) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/url.svg?style=flat-square)](https://packagist.org/packages/spatie/url) -A simple package to deal with URL's in your applications. +A simple package to deal with URLs in your applications. Retrieve parts of the URL: @@ -69,7 +69,7 @@ We highly appreciate you sending us a postcard from your hometown, mentioning wh You can install the package via composer: -``` bash +```bash composer require spatie/url ``` @@ -83,17 +83,21 @@ Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recen ## Testing -``` bash +```bash composer test ``` +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. + ## Contributing -Please see [CONTRIBUTING](CONTRIBUTING.md) for details. +Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. -## Security +## Security Vulnerabilities -If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker. +Please review [our security policy](../../security/policy) on how to report security vulnerabilities. ## Postcardware diff --git a/composer.json b/composer.json index 1ce5f21..d68e134 100644 --- a/composer.json +++ b/composer.json @@ -16,12 +16,12 @@ } ], "require": { - "php": "^7.2|^8.0", + "php": "^8.0", "psr/http-message": "^1.0", - "spatie/macroable": "^1.0.1" + "spatie/macroable": "^2.0" }, "require-dev": { - "phpunit/phpunit": "^8.0|^9.3" + "phpunit/phpunit": "^9.5" }, "autoload": { "psr-4": { @@ -34,9 +34,12 @@ } }, "scripts": { - "test": "vendor/bin/phpunit" + "test": "vendor/bin/phpunit", + "test-coverage": "vendor/bin/phpunit --coverage-html coverage" }, "config": { "sort-packages": true - } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/src/Exceptions/InvalidArgument.php b/src/Exceptions/InvalidArgument.php index 20cbb55..80ed2c6 100644 --- a/src/Exceptions/InvalidArgument.php +++ b/src/Exceptions/InvalidArgument.php @@ -6,12 +6,12 @@ class InvalidArgument extends InvalidArgumentException { - public static function invalidScheme(string $url): self + public static function invalidScheme(string $url): static { return new static("The scheme `{$url}` isn't valid. It should be either `http` or `https`."); } - public static function segmentZeroDoesNotExist() + public static function segmentZeroDoesNotExist(): static { return new static("Segment 0 doesn't exist. Segments can be retrieved by using 1-based index or a negative index."); } diff --git a/src/Helpers/Arr.php b/src/Helpers/Arr.php index 6eae7d8..c09de17 100644 --- a/src/Helpers/Arr.php +++ b/src/Helpers/Arr.php @@ -4,7 +4,7 @@ class Arr { - public static function map(array $items, callable $callback) + public static function map(array $items, callable $callback): array { $keys = array_keys($items); @@ -13,7 +13,7 @@ public static function map(array $items, callable $callback) return array_combine($keys, $items); } - public static function mapToAssoc(array $items, callable $callback) + public static function mapToAssoc(array $items, callable $callback): mixed { return array_reduce($items, function (array $assoc, $item) use ($callback) { [$key, $value] = $callback($item); diff --git a/src/QueryParameterBag.php b/src/QueryParameterBag.php index a618199..4804ac6 100644 --- a/src/QueryParameterBag.php +++ b/src/QueryParameterBag.php @@ -6,15 +6,13 @@ class QueryParameterBag { - /** @var array */ - protected $parameters; - - public function __construct(array $parameters = []) - { - $this->parameters = $parameters; + public function __construct( + protected array $parameters = [], + ) { + // } - public static function fromString(string $query = ''): self + public static function fromString(string $query = ''): static { if ($query === '') { return new static(); @@ -29,7 +27,7 @@ public static function fromString(string $query = ''): self })); } - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { return $this->parameters[$key] ?? $default; } @@ -39,14 +37,14 @@ public function has(string $key): bool return array_key_exists($key, $this->parameters); } - public function set(string $key, string $value) + public function set(string $key, string $value): self { $this->parameters[$key] = $value; return $this; } - public function unset(string $key) + public function unset(string $key): self { unset($this->parameters[$key]); @@ -58,11 +56,12 @@ public function all(): array return $this->parameters; } - public function __toString() + public function __toString(): string { - $keyValuePairs = Arr::map($this->parameters, function ($value, $key) { - return "{$key}=".rawurlencode($value); - }); + $keyValuePairs = Arr::map( + $this->parameters, + fn ($value, $key) => "{$key}=".rawurlencode($value) + ); return implode('&', $keyValuePairs); } diff --git a/src/Url.php b/src/Url.php index cc9dd46..f7f1ed5 100644 --- a/src/Url.php +++ b/src/Url.php @@ -10,43 +10,35 @@ class Url implements UriInterface { use Macroable; - /** @var string */ - protected $scheme = ''; + protected string $scheme = ''; - /** @var string */ - protected $host = ''; + protected string $host = ''; - /** @var int|null */ - protected $port = null; + protected ?int $port = null; - /** @var string */ - protected $user = ''; + protected string $user = ''; - /** @var string|null */ - protected $password = null; + protected ?string $password = null; - /** @var string */ - protected $path = ''; + protected string $path = ''; - /** @var \Spatie\Url\QueryParameterBag */ - protected $query; + protected QueryParameterBag $query; - /** @var string */ - protected $fragment = ''; + protected string $fragment = ''; - const VALID_SCHEMES = ['http', 'https', 'mailto']; + public const VALID_SCHEMES = ['http', 'https', 'mailto']; public function __construct() { $this->query = new QueryParameterBag(); } - public static function create() + public static function create(): static { return new static(); } - public static function fromString(string $url) + public static function fromString(string $url): static { $parts = array_merge(parse_url($url)); @@ -63,12 +55,12 @@ public static function fromString(string $url) return $url; } - public function getScheme() + public function getScheme(): string { return $this->scheme; } - public function getAuthority() + public function getAuthority(): string { $authority = $this->host; @@ -83,7 +75,7 @@ public function getAuthority() return $authority; } - public function getUserInfo() + public function getUserInfo(): string { $userInfo = $this->user; @@ -94,17 +86,17 @@ public function getUserInfo() return $userInfo; } - public function getHost() + public function getHost(): string { return $this->host; } - public function getPort() + public function getPort(): ?int { return $this->port; } - public function getPath() + public function getPath(): string { return $this->path; } @@ -128,7 +120,7 @@ public function getQuery(): string return (string) $this->query; } - public function getQueryParameter(string $key, $default = null) + public function getQueryParameter(string $key, $default = null): mixed { return $this->query->get($key, $default); } @@ -143,7 +135,7 @@ public function getAllQueryParameters(): array return $this->query->all(); } - public function withQueryParameter(string $key, string $value) + public function withQueryParameter(string $key, string $value): self { $url = clone $this; $url->query->unset($key); @@ -153,7 +145,7 @@ public function withQueryParameter(string $key, string $value) return $url; } - public function withoutQueryParameter(string $key) + public function withoutQueryParameter(string $key): self { $url = clone $this; $url->query->unset($key); @@ -161,7 +153,7 @@ public function withoutQueryParameter(string $key) return $url; } - public function getFragment() + public function getFragment(): string { return $this->fragment; } @@ -171,7 +163,7 @@ public function getSegments(): array return explode('/', trim($this->path, '/')); } - public function getSegment(int $index, $default = null) + public function getSegment(int $index, mixed $default = null): mixed { $segments = $this->getSegments(); @@ -187,21 +179,21 @@ public function getSegment(int $index, $default = null) return $segments[$index - 1] ?? $default; } - public function getFirstSegment() + public function getFirstSegment(): mixed { $segments = $this->getSegments(); return $segments[0] ?? null; } - public function getLastSegment() + public function getLastSegment(): mixed { $segments = $this->getSegments(); return end($segments) ?? null; } - public function withScheme($scheme) + public function withScheme($scheme): self { $url = clone $this; @@ -221,7 +213,7 @@ protected function sanitizeScheme(string $scheme): string return $scheme; } - public function withUserInfo($user, $password = null) + public function withUserInfo($user, $password = null): self { $url = clone $this; @@ -231,7 +223,7 @@ public function withUserInfo($user, $password = null) return $url; } - public function withHost($host) + public function withHost($host): self { $url = clone $this; @@ -240,7 +232,7 @@ public function withHost($host) return $url; } - public function withPort($port) + public function withPort($port): self { $url = clone $this; @@ -249,11 +241,11 @@ public function withPort($port) return $url; } - public function withPath($path) + public function withPath($path): self { $url = clone $this; - if (strpos($path, '/') !== 0) { + if (! str_starts_with($path, '/')) { $path = '/'.$path; } @@ -262,7 +254,7 @@ public function withPath($path) return $url; } - public function withDirname(string $dirname) + public function withDirname(string $dirname): self { $dirname = trim($dirname, '/'); @@ -273,7 +265,7 @@ public function withDirname(string $dirname) return $this->withPath($dirname.'/'.$this->getBasename()); } - public function withBasename(string $basename) + public function withBasename(string $basename): self { $basename = trim($basename, '/'); @@ -284,7 +276,7 @@ public function withBasename(string $basename) return $this->withPath($this->getDirname().'/'.$basename); } - public function withQuery($query) + public function withQuery($query): self { $url = clone $this; @@ -293,7 +285,7 @@ public function withQuery($query) return $url; } - public function withFragment($fragment) + public function withFragment($fragment): self { $url = clone $this; @@ -307,11 +299,11 @@ public function matches(self $url): bool return (string) $this === (string) $url; } - public function __toString() + public function __toString(): string { $url = ''; - if ($this->getScheme() !== '' && $this->getScheme() != 'mailto') { + if ($this->getScheme() !== '' && $this->getScheme() !== 'mailto') { $url .= $this->getScheme().'://'; }