diff --git a/.codacy.yml b/.codacy.yml
new file mode 100644
index 0000000..008d53b
--- /dev/null
+++ b/.codacy.yml
@@ -0,0 +1,4 @@
+exclude_paths:
+ - ".github/**"
+ - "example/**"
+ - "test/**"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a7b2b4e..b71fd6f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,6 +4,10 @@ on:
push:
pull_request:
+permissions:
+ contents: read
+ actions: read
+
jobs:
composer:
runs-on: ubuntu-latest
@@ -34,6 +38,7 @@ jobs:
with:
name: build-artifact-${{ matrix.php }}
path: /tmp/github-actions
+ retention-days: 1
phpunit:
runs-on: ubuntu-latest
@@ -89,9 +94,10 @@ jobs:
run: cat "_coverage/coverage.txt"
- name: Upload to Codecov
- uses: codecov/codecov-action@v5
+ uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
+ slug: ${{ github.repository }}
phpstan:
runs-on: ubuntu-latest
@@ -163,21 +169,3 @@ jobs:
php_version: ${{ matrix.php }}
path: src/
standard: phpcs.xml
-
- remove_old_artifacts:
- runs-on: ubuntu-latest
-
- permissions:
- actions: write
-
- steps:
- - name: Remove old artifacts for prior workflow runs on this repository
- env:
- GH_TOKEN: ${{ github.token }}
- run: |
- 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 ... "
- gh api --method DELETE /repos/${{ github.repository }}/actions/artifacts/$id && echo "Done"
- done
-
+
+This package defines:
+
++ `GT\TypeSafeGetter\TypeSafeGetter`
++ `GT\TypeSafeGetter\NullableTypeSafeGetter`
++ `GT\TypeSafeGetter\CallbackTypeSafeGetter`
+
+The usual pattern is to implement `get()` yourself and use the `NullableTypeSafeGetter` trait for the common typed methods.
+
+```php
+use DateTimeImmutable;
+use DateTimeInterface;
+use GT\TypeSafeGetter\NullableTypeSafeGetter;
+use GT\TypeSafeGetter\TypeSafeGetter;
+
+class DataStore implements TypeSafeGetter {
+ use NullableTypeSafeGetter;
+
+ public function __construct(
+ private readonly array $data = [],
+ ) {}
+
+ public function get(string $name):mixed {
+ return $this->data[$name] ?? null;
+ }
+}
+
+$store = new DataStore([
+ "id" => "42",
+ "active" => 1,
+ "created" => "2024-05-01 09:15:00",
+]);
+
+echo $store->getInt("id");
+var_dump($store->getBool("active"));
+echo $store->getDateTime("created")?->format("Y-m-d");
+```
+
+`getInstance()` returns an existing object after checking its type:
+
+```php
+$store = new DataStore([
+ "date" => new DateTimeImmutable("2024-05-01"),
+]);
+
+$date = $store->getInstance("date", DateTimeInterface::class);
+```
+
+`getDateTime()` accepts:
+
++ `DateTimeInterface` instances
++ Unix timestamps
++ date/time strings supported by `DateTimeImmutable`
+
The following methods are defined by this interface:
+ `get(string $name):mixed` - A non-type-safe getter, used for getting keys that are not of an inbuilt type
@@ -36,12 +89,21 @@ Common areas you will see this interface used:
+ Database rows
+ User input (from the query string or posted form data)
+ Session and cookie storage
-+ PHP.Gt's [DataObject](https://www.php.gt/dataobject) repository.
++ HTTP header collections
++ Configuration objects
++ PHP.GT's [DataObject](https://www.php.gt/dataobject) repository.
`NullableTypeSafeGetter` trait
------------------------------
-A lot of repositories within PHP.Gt that utilise this class were repeating the same getter code, so this trait was introduced to remove the repetition. All getter functions of the interface are implemented, introducing a protected helper function `getNullableType` which removes the repetition of checking null values before casting them. The `getNullableType` function also allows a callback to be passed as the type parameter, allowing more complex nullable types to be constructed.
+A lot of repositories within PHP.GT that utilise this class were repeating the same getter code, so this trait was introduced to remove the repetition. All getter functions of the interface are implemented, introducing a protected helper function `getNullableType` which removes the repetition of checking null values before casting them. The `getNullableType` function also allows a callback to be passed as the type parameter, allowing more complex nullable types to be constructed.
+
+`CallbackTypeSafeGetter` interface
+----------------------------------
+
+This interface is for callback-driven lookups where a value may need to be fetched or computed as part of the read operation, such as cache APIs.
+
+Unlike `NullableTypeSafeGetter`, there is no trait implementation in this package for the callback interface. Implementing classes define their own callback behaviour.
# Proudly sponsored by
diff --git a/composer.json b/composer.json
index a45c9cc..5bdcc3d 100644
--- a/composer.json
+++ b/composer.json
@@ -29,14 +29,32 @@
}
],
+ "scripts": {
+ "phpunit": "vendor/bin/phpunit --configuration phpunit.xml",
+ "phpstan": "vendor/bin/phpstan analyse --level 6 src --memory-limit 256M",
+ "phpcs": "vendor/bin/phpcs src --standard=phpcs.xml",
+ "phpmd": "vendor/bin/phpmd src/ text phpmd.xml",
+ "test": [
+ "@phpunit",
+ "@phpstan",
+ "@phpcs",
+ "@phpmd"
+ ]
+ },
+
"autoload": {
"psr-4": {
+ "GT\\TypeSafeGetter\\": "./src",
"Gt\\TypeSafeGetter\\": "./src"
}
},
"autoload-dev": {
"psr-4": {
- "Gt\\TypeSafeGetter\\Test\\": "./test/phpunit"
+ "GT\\TypeSafeGetter\\Test\\": "./test/phpunit"
+ }
+ },
+ "config": {
+ "platform": {
}
}
}
diff --git a/composer.lock b/composer.lock
index fdb220e..72228d3 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "8741f9ae0379e72c81f108cc84f5df9b",
+ "content-hash": "a607ea873e39b7d7b855df8933e7e115",
"packages": [],
"packages-dev": [
{
@@ -2154,34 +2154,34 @@
},
{
"name": "symfony/config",
- "version": "v6.4.34",
+ "version": "v7.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "ce9cb0c0d281aaf188b802d4968e42bfb60701e9"
+ "reference": "d4a277b7a0f26487db16b264d935c617b7d994ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/ce9cb0c0d281aaf188b802d4968e42bfb60701e9",
- "reference": "ce9cb0c0d281aaf188b802d4968e42bfb60701e9",
+ "url": "https://api.github.com/repos/symfony/config/zipball/d4a277b7a0f26487db16b264d935c617b7d994ea",
+ "reference": "d4a277b7a0f26487db16b264d935c617b7d994ea",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
- "symfony/filesystem": "^5.4|^6.0|^7.0",
+ "symfony/filesystem": "^7.1|^8.0",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
- "symfony/finder": "<5.4",
+ "symfony/finder": "<6.4",
"symfony/service-contracts": "<2.5"
},
"require-dev": {
- "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
- "symfony/finder": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^5.4|^6.0|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
+ "symfony/finder": "^6.4|^7.0|^8.0",
+ "symfony/messenger": "^6.4|^7.0|^8.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/yaml": "^5.4|^6.0|^7.0"
+ "symfony/yaml": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
@@ -2209,7 +2209,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/v6.4.34"
+ "source": "https://github.com/symfony/config/tree/v7.4.9"
},
"funding": [
{
@@ -2229,44 +2229,43 @@
"type": "tidelift"
}
],
- "time": "2026-02-24T17:34:50+00:00"
+ "time": "2026-04-29T14:25:20+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v6.4.35",
+ "version": "v7.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "d95712d0e9446b9f244b64811ffb6af7b7434213"
+ "reference": "27cd9f912438d07ced76008bc66cf8b0cf4de622"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d95712d0e9446b9f244b64811ffb6af7b7434213",
- "reference": "d95712d0e9446b9f244b64811ffb6af7b7434213",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/27cd9f912438d07ced76008bc66cf8b0cf4de622",
+ "reference": "27cd9f912438d07ced76008bc66cf8b0cf4de622",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3",
- "symfony/service-contracts": "^2.5|^3.0",
- "symfony/var-exporter": "^6.4.20|^7.2.5"
+ "symfony/service-contracts": "^3.6",
+ "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0"
},
"conflict": {
"ext-psr": "<1.1|>=2",
- "symfony/config": "<6.1",
- "symfony/finder": "<5.4",
- "symfony/proxy-manager-bridge": "<6.3",
- "symfony/yaml": "<5.4"
+ "symfony/config": "<6.4",
+ "symfony/finder": "<6.4",
+ "symfony/yaml": "<6.4"
},
"provide": {
"psr/container-implementation": "1.1|2.0",
"symfony/service-implementation": "1.1|2.0|3.0"
},
"require-dev": {
- "symfony/config": "^6.1|^7.0",
- "symfony/expression-language": "^5.4|^6.0|^7.0",
- "symfony/yaml": "^5.4|^6.0|^7.0"
+ "symfony/config": "^6.4|^7.0|^8.0",
+ "symfony/expression-language": "^6.4|^7.0|^8.0",
+ "symfony/yaml": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
@@ -2294,7 +2293,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/v6.4.35"
+ "source": "https://github.com/symfony/dependency-injection/tree/v7.4.9"
},
"funding": [
{
@@ -2314,7 +2313,7 @@
"type": "tidelift"
}
],
- "time": "2026-02-26T12:16:01+00:00"
+ "time": "2026-04-30T18:38:49+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2385,25 +2384,25 @@
},
{
"name": "symfony/filesystem",
- "version": "v6.4.34",
+ "version": "v7.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3"
+ "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/01ffe0411b842f93c571e5c391f289c3fdd498c3",
- "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/dcd8f96bcdc0f128ec406c765cc066c6035d1be3",
+ "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
- "symfony/process": "^5.4|^6.4|^7.0"
+ "symfony/process": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
@@ -2431,7 +2430,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v6.4.34"
+ "source": "https://github.com/symfony/filesystem/tree/v7.4.9"
},
"funding": [
{
@@ -2451,20 +2450,20 @@
"type": "tidelift"
}
],
- "time": "2026-02-24T17:51:06+00:00"
+ "time": "2026-04-18T13:18:21+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.33.0",
+ "version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ "reference": "141046a8f9477948ff284fa65be2095baafb94f2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
- "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
+ "reference": "141046a8f9477948ff284fa65be2095baafb94f2",
"shasum": ""
},
"require": {
@@ -2514,7 +2513,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
},
"funding": [
{
@@ -2534,20 +2533,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2026-04-10T16:19:22+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.33.0",
+ "version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
- "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
+ "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
"shasum": ""
},
"require": {
@@ -2599,7 +2598,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
},
"funding": [
{
@@ -2619,7 +2618,7 @@
"type": "tidelift"
}
],
- "time": "2024-12-23T08:48:59+00:00"
+ "time": "2026-04-10T17:25:58+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2710,26 +2709,26 @@
},
{
"name": "symfony/var-exporter",
- "version": "v6.4.26",
+ "version": "v7.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
- "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc"
+ "reference": "22e03a49c95ef054a43601cd159b222bfab1c701"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-exporter/zipball/466fcac5fa2e871f83d31173f80e9c2684743bfc",
- "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/22e03a49c95ef054a43601cd159b222bfab1c701",
+ "reference": "22e03a49c95ef054a43601cd159b222bfab1c701",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3"
},
"require-dev": {
- "symfony/property-access": "^6.4|^7.0",
- "symfony/serializer": "^6.4|^7.0",
- "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ "symfony/property-access": "^6.4|^7.0|^8.0",
+ "symfony/serializer": "^6.4|^7.0|^8.0",
+ "symfony/var-dumper": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
@@ -2767,7 +2766,7 @@
"serialize"
],
"support": {
- "source": "https://github.com/symfony/var-exporter/tree/v6.4.26"
+ "source": "https://github.com/symfony/var-exporter/tree/v7.4.9"
},
"funding": [
{
@@ -2787,7 +2786,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T09:57:09+00:00"
+ "time": "2026-04-18T13:18:21+00:00"
},
{
"name": "theseer/tokenizer",
@@ -2849,5 +2848,8 @@
"php": ">=8.0"
},
"platform-dev": {},
+ "platform-overrides": {
+ "php": "8.2"
+ },
"plugin-api-version": "2.9.0"
}
diff --git a/src/CallbackTypeSafeGetter.php b/src/CallbackTypeSafeGetter.php
index a6f9a4f..4b1c7bc 100644
--- a/src/CallbackTypeSafeGetter.php
+++ b/src/CallbackTypeSafeGetter.php
@@ -1,5 +1,5 @@