diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6a4386c..3afd5b1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,16 +7,16 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- php: [ 8.2, 8.3 ]
+ php: [ 8.2, 8.3, 8.4 ]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: /tmp/composer-cache
- key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
+ key: ${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
- name: Composer install
uses: php-actions/composer@v6
@@ -24,12 +24,12 @@ jobs:
php_version: ${{ matrix.php }}
- name: Archive build
- run: mkdir /tmp/github-actions/ && tar -cvf /tmp/github-actions/build.tar ./
+ run: mkdir /tmp/github-actions/ && tar --exclude=".git" -cvf /tmp/github-actions/build.tar ./
- name: Upload build archive for test runners
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
- name: build-artifact
+ name: build-artifact-${{ matrix.php }}
path: /tmp/github-actions
phpunit:
@@ -37,15 +37,15 @@ jobs:
needs: [ composer ]
strategy:
matrix:
- php: [ 8.2, 8.3 ]
+ php: [ 8.2, 8.3, 8.4 ]
outputs:
coverage: ${{ steps.store-coverage.outputs.coverage_text }}
steps:
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
- name: build-artifact
+ name: build-artifact-${{ matrix.php }}
path: /tmp/github-actions
- name: Extract build archive
@@ -56,15 +56,16 @@ jobs:
env:
XDEBUG_MODE: cover
with:
+ version: 10
php_version: ${{ matrix.php }}
php_extensions: xdebug
coverage_text: _coverage/coverage.txt
coverage_clover: _coverage/clover.xml
- name: Store coverage data
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
- name: code-coverage
+ name: code-coverage-${{ matrix.php }}-${{ github.run_number }}
path: _coverage
coverage:
@@ -72,30 +73,32 @@ jobs:
needs: [ phpunit ]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
- name: code-coverage
+ name: code-coverage-${{ matrix.php }}-${{ github.run_number }}
path: _coverage
- name: Output coverage
run: cat "_coverage/coverage.txt"
- name: Upload to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v5
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
phpstan:
runs-on: ubuntu-latest
needs: [ composer ]
strategy:
matrix:
- php: [ 8.2, 8.3 ]
+ php: [ 8.2, 8.3, 8.4 ]
steps:
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
- name: build-artifact
+ name: build-artifact-${{ matrix.php }}
path: /tmp/github-actions
- name: Extract build archive
@@ -112,12 +115,12 @@ jobs:
needs: [ composer ]
strategy:
matrix:
- php: [ 8.2, 8.3 ]
+ php: [ 8.2, 8.3, 8.4 ]
steps:
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
- name: build-artifact
+ name: build-artifact-${{ matrix.php }}
path: /tmp/github-actions
- name: Extract build archive
@@ -136,12 +139,12 @@ jobs:
needs: [ composer ]
strategy:
matrix:
- php: [ 8.2, 8.3 ]
+ php: [ 8.2, 8.3, 8.4 ]
steps:
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
- name: build-artifact
+ name: build-artifact-${{ matrix.php }}
path: /tmp/github-actions
- name: Extract build archive
@@ -162,7 +165,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
- gh api "/repos/${{ github.repository }}/actions/artifacts?name=build-artifact" | jq ".artifacts[] | select(.name == \"build-artifact\") | .id" > artifact-id-list.txt
+ gh api "/repos/${{ github.repository }}/actions/artifacts" | jq ".artifacts[] | select(.name | startswith(\"build-artifact\")) | .id" > artifact-id-list.txt
while read id
do
echo -n "Deleting artifact ID $id ... "
diff --git a/composer.lock b/composer.lock
index c3e48f8..9a02392 100644
--- a/composer.lock
+++ b/composer.lock
@@ -827,16 +827,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.13.0",
+ "version": "1.13.4",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "024473a478be9df5fdaca2c793f2232fe788e414"
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
- "reference": "024473a478be9df5fdaca2c793f2232fe788e414",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
"require": {
@@ -875,7 +875,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
},
"funding": [
{
@@ -883,20 +883,20 @@
"type": "tidelift"
}
],
- "time": "2025-02-12T12:17:51+00:00"
+ "time": "2025-08-01T08:46:24+00:00"
},
{
"name": "nikic/php-parser",
- "version": "v5.4.0",
+ "version": "v5.6.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "447a020a1f875a434d62f2a401f53b82a396e494"
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
- "reference": "447a020a1f875a434d62f2a401f53b82a396e494",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
"shasum": ""
},
"require": {
@@ -915,7 +915,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.0-dev"
+ "dev-master": "5.x-dev"
}
},
"autoload": {
@@ -939,9 +939,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
},
- "time": "2024-12-30T11:07:19+00:00"
+ "time": "2025-10-21T19:32:17+00:00"
},
{
"name": "pdepend/pdepend",
@@ -1209,16 +1209,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.12.24",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "338b92068f58d9f8035b76aed6cf2b9e5624c025"
- },
+ "version": "1.12.32",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/338b92068f58d9f8035b76aed6cf2b9e5624c025",
- "reference": "338b92068f58d9f8035b76aed6cf2b9e5624c025",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8",
+ "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8",
"shasum": ""
},
"require": {
@@ -1263,7 +1258,7 @@
"type": "github"
}
],
- "time": "2025-04-16T13:01:53+00:00"
+ "time": "2025-09-30T10:16:31+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -1588,16 +1583,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "10.5.45",
+ "version": "10.5.58",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "bd68a781d8e30348bc297449f5234b3458267ae8"
+ "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8",
- "reference": "bd68a781d8e30348bc297449f5234b3458267ae8",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca",
+ "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca",
"shasum": ""
},
"require": {
@@ -1607,7 +1602,7 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.12.1",
+ "myclabs/deep-copy": "^1.13.4",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.1",
@@ -1618,13 +1613,13 @@
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
- "sebastian/comparator": "^5.0.3",
+ "sebastian/comparator": "^5.0.4",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
- "sebastian/exporter": "^5.1.2",
+ "sebastian/exporter": "^5.1.4",
"sebastian/global-state": "^6.0.2",
"sebastian/object-enumerator": "^5.0.0",
- "sebastian/recursion-context": "^5.0.0",
+ "sebastian/recursion-context": "^5.0.1",
"sebastian/type": "^4.0.0",
"sebastian/version": "^4.0.1"
},
@@ -1669,7 +1664,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58"
},
"funding": [
{
@@ -1680,12 +1675,20 @@
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift"
}
],
- "time": "2025-02-06T16:08:12+00:00"
+ "time": "2025-09-28T12:04:46+00:00"
},
{
"name": "psr/container",
@@ -1960,16 +1963,16 @@
},
{
"name": "sebastian/comparator",
- "version": "5.0.3",
+ "version": "5.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
+ "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
- "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
+ "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"shasum": ""
},
"require": {
@@ -2025,15 +2028,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+ "type": "tidelift"
}
],
- "time": "2024-10-18T14:56:07+00:00"
+ "time": "2025-09-07T05:25:07+00:00"
},
{
"name": "sebastian/complexity",
@@ -2226,16 +2241,16 @@
},
{
"name": "sebastian/exporter",
- "version": "5.1.2",
+ "version": "5.1.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
+ "reference": "0735b90f4da94969541dac1da743446e276defa6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
- "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6",
+ "reference": "0735b90f4da94969541dac1da743446e276defa6",
"shasum": ""
},
"require": {
@@ -2244,7 +2259,7 @@
"sebastian/recursion-context": "^5.0"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
@@ -2292,15 +2307,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy",
- "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
}
],
- "time": "2024-03-02T07:17:12+00:00"
+ "time": "2025-09-24T06:09:11+00:00"
},
{
"name": "sebastian/global-state",
@@ -2536,23 +2563,23 @@
},
{
"name": "sebastian/recursion-context",
- "version": "5.0.0",
+ "version": "5.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "05909fb5bc7df4c52992396d0116aed689f93712"
+ "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712",
- "reference": "05909fb5bc7df4c52992396d0116aed689f93712",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a",
+ "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
@@ -2587,15 +2614,28 @@
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
- "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0"
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+ "type": "tidelift"
}
],
- "time": "2023-02-03T07:05:40+00:00"
+ "time": "2025-08-10T07:50:56+00:00"
},
{
"name": "sebastian/type",
@@ -2708,16 +2748,16 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.12.2",
+ "version": "3.13.4",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
- "reference": "6d4cf6032d4b718f168c90a96e36c7d0eaacb2aa"
+ "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/6d4cf6032d4b718f168c90a96e36c7d0eaacb2aa",
- "reference": "6d4cf6032d4b718f168c90a96e36c7d0eaacb2aa",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119",
+ "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119",
"shasum": ""
},
"require": {
@@ -2788,20 +2828,20 @@
"type": "thanks_dev"
}
],
- "time": "2025-04-13T04:10:18+00:00"
+ "time": "2025-09-05T05:47:09+00:00"
},
{
"name": "symfony/config",
- "version": "v7.2.3",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "7716594aaae91d9141be080240172a92ecca4d44"
+ "reference": "8a09223170046d2cfda3d2e11af01df2c641e961"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/7716594aaae91d9141be080240172a92ecca4d44",
- "reference": "7716594aaae91d9141be080240172a92ecca4d44",
+ "url": "https://api.github.com/repos/symfony/config/zipball/8a09223170046d2cfda3d2e11af01df2c641e961",
+ "reference": "8a09223170046d2cfda3d2e11af01df2c641e961",
"shasum": ""
},
"require": {
@@ -2847,7 +2887,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v7.2.3"
+ "source": "https://github.com/symfony/config/tree/v7.3.4"
},
"funding": [
{
@@ -2858,25 +2898,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-01-22T12:07:01+00:00"
+ "time": "2025-09-22T12:46:16+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v7.2.5",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "58ab71379f14a741755717cece2868bf41ed45d8"
+ "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/58ab71379f14a741755717cece2868bf41ed45d8",
- "reference": "58ab71379f14a741755717cece2868bf41ed45d8",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/82119812ab0bf3425c1234d413efd1b19bb92ae4",
+ "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4",
"shasum": ""
},
"require": {
@@ -2927,7 +2971,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v7.2.5"
+ "source": "https://github.com/symfony/dependency-injection/tree/v7.3.4"
},
"funding": [
{
@@ -2938,25 +2982,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-03-13T12:21:46+00:00"
+ "time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.5.1",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
- "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": ""
},
"require": {
@@ -2969,7 +3017,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.5-dev"
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -2994,7 +3042,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -3010,20 +3058,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-25T14:20:29+00:00"
+ "time": "2024-09-25T14:21:43+00:00"
},
{
"name": "symfony/filesystem",
- "version": "v7.2.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
+ "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
- "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd",
+ "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd",
"shasum": ""
},
"require": {
@@ -3060,7 +3108,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v7.2.0"
+ "source": "https://github.com/symfony/filesystem/tree/v7.3.2"
},
"funding": [
{
@@ -3071,16 +3119,20 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-10-25T15:15:23+00:00"
+ "time": "2025-07-07T08:17:47+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.31.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -3139,7 +3191,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
},
"funding": [
{
@@ -3150,6 +3202,10 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@@ -3159,19 +3215,20 @@
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.31.0",
+ "version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
+ "ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
@@ -3219,7 +3276,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
},
"funding": [
{
@@ -3230,25 +3287,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2024-12-23T08:48:59+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.5.1",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
- "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
"shasum": ""
},
"require": {
@@ -3266,7 +3327,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.5-dev"
+ "dev-main": "3.6-dev"
}
},
"autoload": {
@@ -3302,7 +3363,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
},
"funding": [
{
@@ -3318,24 +3379,25 @@
"type": "tidelift"
}
],
- "time": "2024-09-25T14:20:29+00:00"
+ "time": "2025-04-25T09:37:31+00:00"
},
{
"name": "symfony/var-exporter",
- "version": "v7.2.5",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
- "reference": "c37b301818bd7288715d40de634f05781b686ace"
+ "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c37b301818bd7288715d40de634f05781b686ace",
- "reference": "c37b301818bd7288715d40de634f05781b686ace",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
+ "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
"shasum": ""
},
"require": {
- "php": ">=8.2"
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3"
},
"require-dev": {
"symfony/property-access": "^6.4|^7.0",
@@ -3378,7 +3440,7 @@
"serialize"
],
"support": {
- "source": "https://github.com/symfony/var-exporter/tree/v7.2.5"
+ "source": "https://github.com/symfony/var-exporter/tree/v7.3.4"
},
"funding": [
{
@@ -3389,12 +3451,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-03-13T12:21:46+00:00"
+ "time": "2025-09-11T10:12:26+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/example/01-get-customer-by-id.php b/example/01-get-customer-by-id.php
index a01a0fe..11995a7 100644
--- a/example/01-get-customer-by-id.php
+++ b/example/01-get-customer-by-id.php
@@ -35,5 +35,5 @@
echo "Last name: $customer->lastName\n";
}
catch(CustomerNotFoundException) {
- echo "No customer was found with the email address $customerId\n";
+ echo "No customer was found with the ID $customerId\n";
}
diff --git a/phpcs.xml b/phpcs.xml
index a80be70..23146e3 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -24,7 +24,6 @@
-
diff --git a/src/Client.php b/src/Client.php
index 18781bb..7321ffa 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -3,10 +3,14 @@
use Gt\Fetch\Http;
use Gt\Http\Response;
+use Gt\Json\JsonDecodeException;
use Gt\Json\JsonObject;
use Gt\Json\JsonPrimitive\JsonArrayPrimitive;
use Gt\Json\JsonPrimitive\JsonNullPrimitive;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
readonly class Client {
const USER_AGENT = "github.com/BrightFlair/SpektrixAPI";
const BASE_URI = "https://system.spektrix.com/{client}/api";
@@ -28,6 +32,15 @@ public function createCustomer(
?string $lastName = null,
?string $mobile = null,
):Customer {
+// This isn't typically the way URL-encoded data will be passed, if + or -
+// characters are present, Spektrix's servers will fail.
+ $email = rawurlencode($email);
+ $email = str_replace("+", "%2b", $email);
+ $firstName = rawurlencode($firstName ?? "");
+ $firstName = str_replace("'", "%27", $firstName);
+ $lastName = rawurlencode($lastName ?? "");
+ $lastName = str_replace("'", "%27", $lastName);
+
$endpoint = Endpoint::createCustomer;
$authenticatedRequest = new AuthenticatedRequest(
$this->secretKey,
@@ -35,8 +48,8 @@ public function createCustomer(
$this->client,
[
"email" => $email,
- "firstName" => $firstName ?? "",
- "lastName" => $lastName ?? "",
+ "firstName" => $firstName,
+ "lastName" => $lastName,
"mobile" => $mobile ?? "",
"birthDate" => ("D, d M Y H:i:s T"),
"friendlyId" => uniqid(),
@@ -256,14 +269,20 @@ private function json(AuthenticatedRequest $authenticatedRequest):?JsonObject {
return null;
}
- throw new SpektrixAPIException("HTTP $response->status");
+ $text = $response->awaitText();
+ throw new SpektrixAPIException("HTTP $response->status - $text");
}
if($response->status === 204) {
return new JsonNullPrimitive();
}
- return $response->awaitJson();
+ try {
+ return $response->awaitJson();
+ }
+ catch(JsonDecodeException $exception) {
+ throw new SpektrixAPIException($exception->getMessage());
+ }
}
}
diff --git a/test/phpunit/ClientTest.php b/test/phpunit/ClientTest.php
index 8d66357..676471d 100644
--- a/test/phpunit/ClientTest.php
+++ b/test/phpunit/ClientTest.php
@@ -8,6 +8,7 @@
use BrightFlair\SpektrixAPI\TagNotFoundException;
use Gt\Fetch\Http;
use Gt\Http\Response;
+use Gt\Http\Stream;
use Gt\Json\JsonObject;
use Gt\Json\JsonObjectBuilder;
use Gt\Json\JsonPrimitive\JsonArrayPrimitive;
@@ -302,6 +303,24 @@ public function testRemoveTagFromCustomer():void {
$sut->removeTagFromCustomer($tagId, $customerId);
}
+ public function testInvalidJson():void {
+ $bodyStream = self::createMock(Stream::class);
+ $bodyStream->method("getContents")
+ ->willReturn("Wait a minute, this ain't JSON!");
+
+ $response = (new Response(200))->withBody($bodyStream);
+
+ $fetchClient = self::createMock(Http::class);
+ $fetchClient->expects(self::once())
+ ->method("awaitFetch")
+ ->willReturn($response);
+
+ $sut = new Client(self::TEST_USERNAME, self::TEST_CLIENT, self::TEST_SECRET_KEY, $fetchClient);
+ self::expectException(SpektrixAPIException::class);
+ self::expectExceptionMessage("Error decoding JSON: Syntax error");
+ $sut->getCustomer(id: "ABC-123");
+ }
+
private function getFetchClient(
string $uri,
int $status,