From cc41720a7902dee5d67f6b6d1c3c09672ca50558 Mon Sep 17 00:00:00 2001 From: Alex Standiford Date: Sun, 20 Sep 2026 17:42:02 -0400 Subject: [PATCH] Add identity query capability to database handlers --- .github/workflows/phpstan.yml | 17 +- .github/workflows/phpunit.yml | 18 +- .wordlist.txt | 18 +- composer.json | 1 + composer.lock | 42 +- .../IdentifiableDatabaseDatastoreHandler.php | 5 +- lib/Traits/WithDatastoreHandlerMethods.php | 101 +- phpstan-baseline.neon | 926 ++++++++++++++++++ phpstan.neon.dist | 2 + ...entifiableDatabaseDatastoreHandlerTest.php | 361 +++++++ 10 files changed, 1470 insertions(+), 21 deletions(-) create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon.dist create mode 100644 tests/Unit/Abstracts/IdentifiableDatabaseDatastoreHandlerTest.php diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 3681a3d..1e0d4e8 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -6,12 +6,23 @@ jobs: phpstan: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: php-actions/composer@v6 + - uses: actions/checkout@v4 + + - name: Check out Datastore prerequisite + uses: actions/checkout@v4 + with: + repository: phpnomad/datastore + ref: codex/ci-prerequisites-clean + path: .ci/datastore + + - name: Install dependencies with Datastore prerequisite + run: | + composer config repositories.datastore '{"type":"path","url":".ci/datastore","options":{"symlink":false,"versions":{"phpnomad/datastore":"2.0.99"}}}' + composer update phpnomad/datastore --with-dependencies --no-interaction --prefer-dist - name: PHPStan Static Analysis uses: php-actions/phpstan@v3 with: path: lib/ tests/ level: 9 - php_version: 8.1 \ No newline at end of file + php_version: 8.2 diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 93c7b1a..e3a648b 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -7,11 +7,23 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: php-actions/composer@v6 + - name: Check out Datastore prerequisite + uses: actions/checkout@v4 + with: + repository: phpnomad/datastore + ref: codex/ci-prerequisites-clean + path: .ci/datastore + + - name: Install dependencies with Datastore prerequisite + run: | + composer config repositories.datastore '{"type":"path","url":".ci/datastore","options":{"symlink":false,"versions":{"phpnomad/datastore":"2.0.99"}}}' + composer update phpnomad/datastore --with-dependencies --no-interaction --prefer-dist - name: PHPUnit Tests uses: php-actions/phpunit@v3 with: - configuration: phpunit.xml \ No newline at end of file + configuration: phpunit.xml + version: composer + php_version: 8.2 diff --git a/.wordlist.txt b/.wordlist.txt index 7261aef..b650614 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -23,4 +23,20 @@ Traceback nodejs npm fediverse -readme \ No newline at end of file +readme +phpnomad +datastore +schemas +lookups +DateCreatedFactory +DateModifiedFactory +PHPNomad +PrimaryKeyFactory +PostsTable +getUnprefixedName +DatabaseServiceProvider +IdentifiableDatabaseDatastoreHandler +TableSchemaService +WithDatastoreHandlerMethods +PostDatabaseDatastoreHandler +txt diff --git a/composer.json b/composer.json index dd909d2..519ca1d 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "phpnomad/cache": "^1.0", "phpnomad/chrono": "^1.0", "phpnomad/datastore": "^2.0", + "phpnomad/event": "^1.0", "phpnomad/singleton": "^1.0", "phpnomad/logger": "^1.0" }, diff --git a/composer.lock b/composer.lock index 7e0ca1c..d798178 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": "bd34040462bfd6ac5f170bd83fa1d6b5", + "content-hash": "638b813cd6561a46cabaad819171e33a", "packages": [ { "name": "phpnomad/cache", @@ -187,6 +187,46 @@ }, "time": "2025-01-13T11:51:33+00:00" }, + { + "name": "phpnomad/event", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpnomad/event.git", + "reference": "b418d8fc90af18b7cb37e118795f9dd92ffeb538" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpnomad/event/zipball/b418d8fc90af18b7cb37e118795f9dd92ffeb538", + "reference": "b418d8fc90af18b7cb37e118795f9dd92ffeb538", + "shasum": "" + }, + "require-dev": { + "phpnomad/tests": "^0.1.0 || ^0.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPNomad\\Events\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Standiford", + "email": "alex@standiford.us" + } + ], + "homepage": "https://github.com/phpnomad/core", + "support": { + "issues": "https://github.com/phpnomad/event/issues", + "source": "https://github.com/phpnomad/event/tree/1.0.1" + }, + "time": "2026-06-12T10:56:48+00:00" + }, { "name": "phpnomad/logger", "version": "1.0.0", diff --git a/lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php b/lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php index ccbf7ed..1788570 100644 --- a/lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php +++ b/lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php @@ -8,10 +8,11 @@ use PHPNomad\Datastore\Interfaces\DataModel; use PHPNomad\Datastore\Interfaces\Datastore; use PHPNomad\Datastore\Interfaces\DatastoreHasCounts; +use PHPNomad\Datastore\Interfaces\DatastoreHasIdentityQuery; use PHPNomad\Datastore\Interfaces\DatastoreHasPrimaryKey; use PHPNomad\Datastore\Interfaces\DatastoreHasWhere; -abstract class IdentifiableDatabaseDatastoreHandler implements Datastore, DatastoreHasPrimaryKey, DatastoreHasWhere, DatastoreHasCounts +abstract class IdentifiableDatabaseDatastoreHandler implements Datastore, DatastoreHasPrimaryKey, DatastoreHasWhere, DatastoreHasCounts, DatastoreHasIdentityQuery { use WithDatastoreHandlerMethods; @@ -45,4 +46,4 @@ public function update($id, array $attributes): void { $this->updateCompound(['id' => $id], $attributes); } -} \ No newline at end of file +} diff --git a/lib/Traits/WithDatastoreHandlerMethods.php b/lib/Traits/WithDatastoreHandlerMethods.php index dbd3ac6..1975094 100644 --- a/lib/Traits/WithDatastoreHandlerMethods.php +++ b/lib/Traits/WithDatastoreHandlerMethods.php @@ -2,6 +2,7 @@ namespace PHPNomad\Database\Traits; +use InvalidArgumentException; use PHPNomad\Cache\Enums\Operation; use PHPNomad\Datastore\Events\RecordCreated; use PHPNomad\Datastore\Events\RecordDeleted; @@ -347,30 +348,108 @@ protected function hydrateItems(array $data): array } /** - * @param array $conditions + * @param array $conditions * @param int|null $limit * @param int|null $offset - * @return array + * @return array * @throws DatastoreErrorException + * @throws InvalidArgumentException */ public function findIds(array $conditions, ?int $limit = null, ?int $offset = null): array { - $this->serviceProvider->queryBuilder - ->from($this->table) - ->select(...$this->table->getFieldsForIdentity()); + $this->validateIdentityQuery($conditions, $limit, $offset); + + $this->serviceProvider->clauseBuilder->reset(); + $this->initiateQuery( + $limit, + $offset, + null, + 'ASC', + $this->table->getFieldsForIdentity() + ); + $this->buildConditions($conditions); - if ($limit) { - $this->serviceProvider->queryBuilder->limit($limit); + return $this->serviceProvider->queryStrategy->query($this->serviceProvider->queryBuilder); + } + + /** + * Validate the optional identity-query contract without changing the behavior of other query methods. + * + * @param array $conditions + */ + protected function validateIdentityQuery(array $conditions, ?int $limit, ?int $offset): void + { + if ($limit !== null && $limit <= 0) { + throw new InvalidArgumentException('The identity-query limit must be greater than zero.'); } - if ($offset) { - $this->serviceProvider->queryBuilder->offset($offset); + if ($offset !== null && $offset < 0) { + throw new InvalidArgumentException('The identity-query offset must not be negative.'); } - $this->buildConditions($conditions); + if ($conditions === []) { + return; + } - return $this->serviceProvider->queryStrategy->query($this->serviceProvider->queryBuilder); + if (!$this->isIdentityQueryList($conditions)) { + throw new InvalidArgumentException('Identity-query conditions must be a list of groups.'); + } + + foreach ($conditions as $group) { + if (!is_array($group) + || !array_key_exists('clauses', $group) + || !is_array($group['clauses']) + || !$this->isIdentityQueryList($group['clauses']) + || $group['clauses'] === [] + ) { + throw new InvalidArgumentException('Each identity-query group must contain a non-empty list of clauses.'); + } + + foreach (['type', 'groupType'] as $key) { + if (array_key_exists($key, $group) && !is_string($group[$key])) { + throw new InvalidArgumentException(sprintf('Identity-query group "%s" must be a string.', $key)); + } + } + + foreach ($group['clauses'] as $clause) { + if (!is_array($clause) + || !array_key_exists('column', $clause) + || !array_key_exists('operator', $clause) + || !is_string($clause['operator']) + || $clause['operator'] === '' + ) { + throw new InvalidArgumentException('Each identity-query clause must contain a column and string operator.'); + } + + $column = $clause['column']; + if (is_string($column)) { + if ($column === '') { + throw new InvalidArgumentException('Each identity-query clause column must be a non-empty string or non-empty list of strings.'); + } + + continue; + } + + if (!is_array($column) || !$this->isIdentityQueryList($column) || $column === []) { + throw new InvalidArgumentException('Each identity-query clause column must be a non-empty string or non-empty list of strings.'); + } + + foreach ($column as $field) { + if (!is_string($field) || $field === '') { + throw new InvalidArgumentException('Each identity-query clause column must be a non-empty string or non-empty list of strings.'); + } + } + } + } + } + + /** + * @param array $values + */ + protected function isIdentityQueryList(array $values): bool + { + return array_values($values) === $values; } /** diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..18afd13 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,926 @@ +parameters: + ignoreErrors: + - + message: "#^Access to constant class on an unknown class PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Call to method getIdentity\\(\\) on an unknown class PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Cannot access constant class on mixed\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:andWhere\\(\\) has invalid return type PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:buildConditions\\(\\) has parameter \\$groups with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:countWhere\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:deleteWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:find\\(\\) should return PHPNomad\\\\Datastore\\\\Interfaces\\\\DataModel but returns mixed\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:findBy\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:findBy\\(\\) should return PHPNomad\\\\Datastore\\\\Interfaces\\\\DataModel but returns mixed\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:findMultiple\\(\\) has parameter \\$ids with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:findMultiple\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:getCacheContextForItem\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:getDuplicates\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:getDuplicates\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:getModels\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:hydrateItems\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:initiateQuery\\(\\) has parameter \\$select with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateIdentity\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateIdentity\\(\\) has parameter \\$fields with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateUniqueFields\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateUniqueFields\\(\\) has parameter \\$updateIdentity with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:orWhere\\(\\) has invalid return type PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) has parameter \\$fields with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:where\\(\\) has invalid return type PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$array of function array_shift expects array, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$array of method PHPNomad\\\\Datastore\\\\Interfaces\\\\ModelAdapter\\\\:\\:toModel\\(\\) expects array, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$conditions of method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:countWhere\\(\\) expects array\\{type\\: string, clauses\\: array\\\\}, array\\{array\\{type\\: 'AND', clauses\\: array\\\\}\\} given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$conditions of method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:countWhere\\(\\) expects array\\{type\\: string, clauses\\: array\\\\}, array\\{array\\{type\\: 'OR', clauses\\: array\\\\}\\} given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:andWhere\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:orWhere\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:where\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$ids of method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:findFromCompound\\(\\) expects non\\-empty\\-array\\, array given\\.$#" + count: 2 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$ids of method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:findFromCompound\\(\\) expects non\\-empty\\-array\\, array\\ given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$ids of method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:findFromCompound\\(\\) expects non\\-empty\\-array\\, array\\{id\\: mixed\\} given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$ids of method PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:updateCompound\\(\\) expects array\\, array\\ given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$limit of method PHPNomad\\\\Database\\\\Interfaces\\\\QueryBuilder\\:\\:limit\\(\\) expects int\\<1, max\\>, int\\\\|int\\<1, max\\> given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$offset of method PHPNomad\\\\Database\\\\Interfaces\\\\QueryBuilder\\:\\:offset\\(\\) expects int\\<1, max\\>, int\\\\|int\\<1, max\\> given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$string of function strtoupper expects string, mixed given\\.$#" + count: 3 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$subject of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects array, mixed given\\.$#" + count: 6 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$key of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects string, int given\\.$#" + count: 2 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:andWhere\\(\\) expects string, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:orWhere\\(\\) expects string, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:where\\(\\) expects string, mixed given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, array given\\.$#" + count: 2 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, int given\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, string given\\.$#" + count: 2 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Abstracts\\\\IdentifiableDatabaseDatastoreHandler\\:\\:\\$modelAdapter with generic interface PHPNomad\\\\Datastore\\\\Interfaces\\\\ModelAdapter does not specify its types\\: TModel$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Variable \\$ids in empty\\(\\) always exists and is not falsy\\.$#" + count: 1 + path: lib/Abstracts/IdentifiableDatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\JunctionTable\\:\\:getAlias\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\JunctionTable\\:\\:getTables\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\JunctionTable\\:\\:getUnprefixedName\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^PHPDoc tag @var for property PHPNomad\\\\Database\\\\Abstracts\\\\JunctionTable\\:\\:\\$logger with type mixed is not subtype of native type PHPNomad\\\\Logger\\\\Interfaces\\\\LoggerStrategy\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^Parameter \\#1 \\$localPrefixProvider of method PHPNomad\\\\Database\\\\Abstracts\\\\Table\\:\\:__construct\\(\\) expects PHPNomad\\\\Database\\\\Interfaces\\\\HasLocalDatabasePrefix, mixed given\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Abstracts\\\\JunctionTable\\:\\:\\$leftTable \\(PHPNomad\\\\Database\\\\Abstracts\\\\Table\\) does not accept mixed\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Abstracts\\\\JunctionTable\\:\\:\\$logger \\(PHPNomad\\\\Logger\\\\Interfaces\\\\LoggerStrategy\\) does not accept mixed\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Abstracts\\\\JunctionTable\\:\\:\\$rightTable \\(PHPNomad\\\\Database\\\\Abstracts\\\\Table\\) does not accept mixed\\.$#" + count: 1 + path: lib/Abstracts/JunctionTable.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Abstracts\\\\Table\\:\\:getFieldsForIdentity\\(\\) should return non\\-empty\\-array\\ but returns array\\.$#" + count: 1 + path: lib/Abstracts/Table.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\Column\\:\\:__construct\\(\\) has parameter \\$attributes with no type specified\\.$#" + count: 1 + path: lib/Factories/Column.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\Column\\:\\:__construct\\(\\) has parameter \\$typeArgs with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Column.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\Column\\:\\:getTypeArgs\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Column.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Factories\\\\Column\\:\\:\\$attributes type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Column.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Factories\\\\Column\\:\\:\\$typeArgs type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Column.php + + - + message: "#^Access to constant class on an unknown class PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Call to method getIdentity\\(\\) on an unknown class PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Cannot access constant class on mixed\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:andWhere\\(\\) has invalid return type PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:buildConditions\\(\\) has parameter \\$groups with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:countWhere\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:deleteWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:findBy\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:findBy\\(\\) should return PHPNomad\\\\Datastore\\\\Interfaces\\\\DataModel but returns mixed\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:getCacheContextForItem\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:getDuplicates\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:getDuplicates\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:getModels\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:hydrateItems\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:initiateQuery\\(\\) has parameter \\$select with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateIdentity\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateIdentity\\(\\) has parameter \\$fields with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateUniqueFields\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:maybeThrowForDuplicateUniqueFields\\(\\) has parameter \\$updateIdentity with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:orWhere\\(\\) has invalid return type PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) has parameter \\$fields with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:where\\(\\) has invalid return type PHPNomad\\\\Datastore\\\\Interfaces\\\\T\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$array of function array_shift expects array, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$array of method PHPNomad\\\\Datastore\\\\Interfaces\\\\ModelAdapter\\\\:\\:toModel\\(\\) expects array, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$conditions of method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:countWhere\\(\\) expects array\\{type\\: string, clauses\\: array\\\\}, array\\{array\\{type\\: 'AND', clauses\\: array\\\\}\\} given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$conditions of method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:countWhere\\(\\) expects array\\{type\\: string, clauses\\: array\\\\}, array\\{array\\{type\\: 'OR', clauses\\: array\\\\}\\} given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:andWhere\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:orWhere\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:where\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$ids of method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:findFromCompound\\(\\) expects non\\-empty\\-array\\, array given\\.$#" + count: 2 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$ids of method PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:findFromCompound\\(\\) expects non\\-empty\\-array\\, array\\ given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$limit of method PHPNomad\\\\Database\\\\Interfaces\\\\QueryBuilder\\:\\:limit\\(\\) expects int\\<1, max\\>, int\\\\|int\\<1, max\\> given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$offset of method PHPNomad\\\\Database\\\\Interfaces\\\\QueryBuilder\\:\\:offset\\(\\) expects int\\<1, max\\>, int\\\\|int\\<1, max\\> given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$string of function strtoupper expects string, mixed given\\.$#" + count: 3 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#1 \\$subject of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects array, mixed given\\.$#" + count: 6 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$key of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects string, int given\\.$#" + count: 2 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:andWhere\\(\\) expects string, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:orWhere\\(\\) expects string, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:where\\(\\) expects string, mixed given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, array given\\.$#" + count: 2 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, int given\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, string given\\.$#" + count: 2 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Factories\\\\DatabaseDatastoreHandler\\:\\:\\$modelAdapter with generic interface PHPNomad\\\\Datastore\\\\Interfaces\\\\ModelAdapter does not specify its types\\: TModel$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Variable \\$ids in empty\\(\\) always exists and is not falsy\\.$#" + count: 1 + path: lib/Factories/DatabaseDatastoreHandler.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\Index\\:\\:__construct\\(\\) has parameter \\$attributes with no type specified\\.$#" + count: 1 + path: lib/Factories/Index.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\Index\\:\\:__construct\\(\\) has parameter \\$columns with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Index.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Factories\\\\Index\\:\\:getAttributes\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Index.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Factories\\\\Index\\:\\:\\$attributes type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Index.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Factories\\\\Index\\:\\:\\$columns type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Factories/Index.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Interfaces\\\\DatabaseContextProvider\\:\\:getModelAdapter\\(\\) has invalid return type PHPNomad\\\\Database\\\\Interfaces\\\\ModelAdapter\\.$#" + count: 2 + path: lib/Interfaces/DatabaseContextProvider.php + + - + message: "#^PHPDoc tag @param references unknown parameter\\: \\$tableName$#" + count: 2 + path: lib/Interfaces/JunctionTableQueryService.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Interfaces\\\\QueryStrategy\\:\\:insert\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Interfaces/QueryStrategy.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Interfaces\\\\QueryStrategy\\:\\:query\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Interfaces/QueryStrategy.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Interfaces\\\\QueryStrategy\\:\\:update\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Interfaces/QueryStrategy.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Services\\\\TableSchemaService\\:\\:getCacheContext\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Services/TableSchemaService.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Services\\\\TableSchemaService\\:\\:getPrimaryColumnNameForTable\\(\\) should return PHPNomad\\\\Database\\\\Factories\\\\Column but returns mixed\\.$#" + count: 1 + path: lib/Services/TableSchemaService.php + + - + message: "#^PHPDoc tag @param references unknown parameter\\: \\$table$#" + count: 1 + path: lib/Services/TableSchemaService.php + + - + message: "#^PHPDoc tag @return with type mixed is not subtype of native type PHPNomad\\\\Database\\\\Factories\\\\Column\\.$#" + count: 1 + path: lib/Services/TableSchemaService.php + + - + message: "#^Trying to invoke \\(callable\\(\\)\\: mixed\\)\\|null but it might not be a callable\\.$#" + count: 1 + path: tests/Unit/Factories/Columns/DateCreatedFactoryTest.php + + - + message: "#^Trying to invoke \\(callable\\(\\)\\: mixed\\)\\|null but it might not be a callable\\.$#" + count: 1 + path: tests/Unit/Factories/Columns/DateModifiedFactoryTest.php + + - + message: "#^Cannot access constant class on mixed\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:__construct\\(\\) has parameter \\$modelAdapter with generic interface PHPNomad\\\\Datastore\\\\Interfaces\\\\ModelAdapter but does not specify its types\\: TModel$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:andWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:andWhere\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:buildConditions\\(\\) has parameter \\$groups with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:countAndWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:countOrWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:countWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:countWhere\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:create\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:deleteWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:exposeCacheContext\\(\\) has parameter \\$ids with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:exposeCacheContext\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:findBy\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:findBy\\(\\) should return PHPNomad\\\\Datastore\\\\Interfaces\\\\DataModel but returns mixed\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:findByIdentity\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:findByIdentity\\(\\) has parameter \\$ids with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:getCacheContextForItem\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:getDuplicates\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:getModels\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:hydrateItems\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:initiateQuery\\(\\) has parameter \\$select with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:maybeThrowForDuplicateIdentity\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:maybeThrowForDuplicateIdentity\\(\\) has parameter \\$fields with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:maybeThrowForDuplicateUniqueFields\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:maybeThrowForDuplicateUniqueFields\\(\\) has parameter \\$updateIdentity with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:orWhere\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:orWhere\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) has parameter \\$fields with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:removeIdentifiableFields\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:updateCompound\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:updateCompound\\(\\) has parameter \\$ids with no type specified\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:where\\(\\) has parameter \\$conditions with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:where\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$array of function array_shift expects array, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$array of method PHPNomad\\\\Datastore\\\\Interfaces\\\\ModelAdapter\\\\:\\:toModel\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:andWhere\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:orWhere\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$field of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:where\\(\\) expects array\\\\|string, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$ids of method PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:findFromCompound\\(\\) expects non\\-empty\\-array\\, array given\\.$#" + count: 3 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$limit of method PHPNomad\\\\Database\\\\Interfaces\\\\QueryBuilder\\:\\:limit\\(\\) expects int\\<1, max\\>, int\\\\|int\\<1, max\\> given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$offset of method PHPNomad\\\\Database\\\\Interfaces\\\\QueryBuilder\\:\\:offset\\(\\) expects int\\<1, max\\>, int\\\\|int\\<1, max\\> given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$string of function strtoupper expects string, mixed given\\.$#" + count: 3 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#1 \\$subject of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects array, mixed given\\.$#" + count: 6 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#2 \\$key of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects string, int given\\.$#" + count: 2 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:andWhere\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:orWhere\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#2 \\$operator of method PHPNomad\\\\Database\\\\Interfaces\\\\ClauseBuilder\\:\\:where\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, array given\\.$#" + count: 2 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, int given\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Parameter \\#3 \\$default of static method PHPNomad\\\\Utils\\\\Helpers\\\\Arr\\:\\:get\\(\\) expects null, string given\\.$#" + count: 2 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:\\$model \\(class\\-string\\\\) does not accept string\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Property PHPNomad\\\\Database\\\\Tests\\\\Unit\\\\Traits\\\\DummyDatastoreHandler\\:\\:\\$modelAdapter with generic interface PHPNomad\\\\Datastore\\\\Interfaces\\\\ModelAdapter does not specify its types\\: TModel$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php + + - + message: "#^Variable \\$ids in empty\\(\\) always exists and is not falsy\\.$#" + count: 1 + path: tests/Unit/Traits/WithDatastoreHandlerMethodsTest.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..07a9980 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,2 @@ +includes: + - phpstan-baseline.neon diff --git a/tests/Unit/Abstracts/IdentifiableDatabaseDatastoreHandlerTest.php b/tests/Unit/Abstracts/IdentifiableDatabaseDatastoreHandlerTest.php new file mode 100644 index 0000000..c2ad661 --- /dev/null +++ b/tests/Unit/Abstracts/IdentifiableDatabaseDatastoreHandlerTest.php @@ -0,0 +1,361 @@ +makeHandler( + new RecordingIdentityQueryBuilder(), + new RecordingIdentityClauseBuilder(), + $this->createMock(QueryStrategy::class) + ); + + self::assertInstanceOf(DatastoreHasIdentityQuery::class, $handler); + } + + public function testFindIdsResetsStateAndBuildsAnIdentityOnlyQuery(): void + { + $queryBuilder = new RecordingIdentityQueryBuilder(); + $clauseBuilder = new RecordingIdentityClauseBuilder(); + $queryStrategy = $this->createMock(QueryStrategy::class); + $expected = [['tenantId' => 10, 'recordId' => 42]]; + $queryStrategy->expects(self::once()) + ->method('query') + ->with(self::identicalTo($queryBuilder)) + ->willReturn($expected); + [$handler, $table] = $this->makeHandler($queryBuilder, $clauseBuilder, $queryStrategy); + + $result = $handler->findIds([[ + 'type' => 'unexpected', + 'groupType' => 'unexpected', + 'clauses' => [[ + 'column' => 'deletedAt', + 'operator' => 'IS NULL', + ]], + ]], 2, 3); + + self::assertSame($expected, $result); + self::assertSame(1, $queryBuilder->resetCalls); + self::assertSame($table, $queryBuilder->table); + self::assertSame(['tenantId', 'recordId'], $queryBuilder->selectedFields); + self::assertSame(2, $queryBuilder->limitValue); + self::assertSame(3, $queryBuilder->offsetValue); + self::assertSame(1, $clauseBuilder->resetCalls); + self::assertCount(1, $clauseBuilder->groups); + self::assertSame('AND', $clauseBuilder->groups[0]['logic']); + self::assertInstanceOf(RecordingIdentityClauseBuilder::class, $clauseBuilder->groups[0]['builder']); + self::assertSame([ + ['method' => 'where', 'field' => 'deletedAt', 'operator' => 'IS NULL', 'values' => []], + ], $clauseBuilder->groups[0]['builder']->predicates); + self::assertSame($clauseBuilder, $queryBuilder->whereClause); + } + + public function testFindIdsAllowsAnUnfilteredQueryWithNullLimitAndZeroOffset(): void + { + $queryBuilder = new RecordingIdentityQueryBuilder(); + $clauseBuilder = new RecordingIdentityClauseBuilder(); + $queryStrategy = $this->createMock(QueryStrategy::class); + $queryStrategy->expects(self::once()) + ->method('query') + ->with(self::identicalTo($queryBuilder)) + ->willReturn([]); + [$handler] = $this->makeHandler($queryBuilder, $clauseBuilder, $queryStrategy); + + self::assertSame([], $handler->findIds([], null, 0)); + self::assertSame(['tenantId', 'recordId'], $queryBuilder->selectedFields); + self::assertNull($queryBuilder->limitValue); + self::assertNull($queryBuilder->offsetValue); + self::assertNull($queryBuilder->whereClause); + } + + /** + * @param array $conditions + * @dataProvider invalidIdentityQueries + */ + public function testFindIdsRejectsInvalidInputBeforeQueryExecution(array $conditions, ?int $limit, ?int $offset): void + { + $queryBuilder = new RecordingIdentityQueryBuilder(); + $clauseBuilder = new RecordingIdentityClauseBuilder(); + $queryStrategy = $this->createMock(QueryStrategy::class); + $queryStrategy->expects(self::never())->method('query'); + [$handler] = $this->makeHandler($queryBuilder, $clauseBuilder, $queryStrategy); + + try { + $handler->findIds($conditions, $limit, $offset); + self::fail('Expected invalid identity-query input to be rejected.'); + } catch (InvalidArgumentException $exception) { + self::assertNotSame('', $exception->getMessage()); + } + + self::assertSame(0, $queryBuilder->resetCalls); + self::assertSame(0, $clauseBuilder->resetCalls); + } + + /** @return array, int|null, int|null}> */ + public static function invalidIdentityQueries(): array + { + $validClause = ['column' => 'id', 'operator' => '=']; + + return [ + 'zero limit' => [[], 0, null], + 'negative limit' => [[], -1, null], + 'negative offset' => [[], null, -1], + 'conditions are not a list' => [['group' => ['clauses' => [$validClause]]], null, null], + 'group is not an array' => [['group'], null, null], + 'group has no clauses' => [[[]], null, null], + 'group has empty clauses' => [[['clauses' => []]], null, null], + 'clauses are not a list' => [[['clauses' => ['first' => $validClause]]], null, null], + 'clause is not an array' => [[['clauses' => ['clause']]], null, null], + 'clause has no column' => [[['clauses' => [['operator' => '=']]]], null, null], + 'clause has no operator' => [[['clauses' => [['column' => 'id']]]], null, null], + 'operator is not a string' => [[['clauses' => [['column' => 'id', 'operator' => 3]]]], null, null], + 'operator is empty' => [[['clauses' => [['column' => 'id', 'operator' => '']]]], null, null], + 'column is not a string or list' => [[['clauses' => [['column' => 3, 'operator' => '=']]]], null, null], + 'column string is empty' => [[['clauses' => [['column' => '', 'operator' => '=']]]], null, null], + 'column list is empty' => [[['clauses' => [['column' => [], 'operator' => '=']]]], null, null], + 'columns are not a list' => [[['clauses' => [['column' => ['id' => 'id'], 'operator' => '=']]]], null, null], + 'column list contains non-string' => [[['clauses' => [['column' => ['id', 3], 'operator' => '=']]]], null, null], + 'column list contains empty string' => [[['clauses' => [['column' => ['id', ''], 'operator' => '=']]]], null, null], + 'type is not a string' => [[['type' => null, 'clauses' => [$validClause]]], null, null], + 'group type is not a string' => [[['groupType' => [], 'clauses' => [$validClause]]], null, null], + ]; + } + + /** + * @return array{IdentityQueryHandlerFixture, Table} + */ + private function makeHandler( + QueryBuilder $queryBuilder, + ClauseBuilder $clauseBuilder, + QueryStrategy $queryStrategy + ): array { + $table = $this->createMock(Table::class); + $table->method('getFieldsForIdentity')->willReturn(['tenantId', 'recordId']); + $serviceProvider = new DatabaseServiceProvider( + $this->createMock(LoggerStrategy::class), + $queryStrategy, + $queryBuilder, + $clauseBuilder, + $this->createMock(CacheableService::class), + $this->createMock(EventStrategy::class) + ); + + return [new IdentityQueryHandlerFixture($serviceProvider, $table), $table]; + } +} + +final class IdentityQueryHandlerFixture extends IdentifiableDatabaseDatastoreHandler +{ + public function __construct(DatabaseServiceProvider $serviceProvider, Table $table) + { + $this->serviceProvider = $serviceProvider; + $this->table = $table; + } +} + +final class RecordingIdentityQueryBuilder implements QueryBuilder +{ + public int $resetCalls = 0; + + /** @var list */ + public array $selectedFields = ['staleField']; + + public ?Table $table = null; + + public ?ClauseBuilder $whereClause = null; + + public ?int $limitValue = 99; + + public ?int $offsetValue = 99; + + public function useTable(Table $table) + { + $this->table = $table; + return $this; + } + + public function select(string $field, string ...$fields) + { + $this->selectedFields = array_values(array_merge($this->selectedFields, [$field], $fields)); + return $this; + } + + public function from(Table $table) + { + $this->table = $table; + return $this; + } + + public function where(?ClauseBuilder $clauseBuilder) + { + $this->whereClause = $clauseBuilder; + return $this; + } + + public function leftJoin(Table $table, string $column, string $onColumn) + { + return $this; + } + + public function rightJoin(Table $table, string $column, string $onColumn) + { + return $this; + } + + public function groupBy(string $column, string ...$columns) + { + return $this; + } + + public function sum(string $fieldToSum, ?string $alias = null) + { + return $this; + } + + public function count(string $fieldToCount, ?string $alias = null) + { + return $this; + } + + public function limit(int $limit) + { + $this->limitValue = $limit; + return $this; + } + + public function offset(int $offset) + { + $this->offsetValue = $offset; + return $this; + } + + public function orderBy(string $field, string $order) + { + return $this; + } + + public function build(): string + { + return 'identity query'; + } + + public function reset() + { + ++$this->resetCalls; + $this->selectedFields = []; + $this->table = null; + $this->whereClause = null; + $this->limitValue = null; + $this->offsetValue = null; + return $this; + } + + public function resetClauses(string $clause, string ...$clauses) + { + return $this; + } +} + +final class RecordingIdentityClauseBuilder implements ClauseBuilder +{ + public int $resetCalls = 0; + + public ?Table $table = null; + + /** @var list}> */ + public array $predicates = []; + + /** @var list */ + public array $groups = []; + + public function useTable(Table $table) + { + $this->table = $table; + return $this; + } + + public function where($field, string $operator, ...$values) + { + $this->predicates[] = [ + 'method' => 'where', + 'field' => $field, + 'operator' => $operator, + 'values' => $values, + ]; + return $this; + } + + public function andWhere($field, string $operator, ...$values) + { + $this->predicates[] = [ + 'method' => 'andWhere', + 'field' => $field, + 'operator' => $operator, + 'values' => $values, + ]; + return $this; + } + + public function orWhere($field, string $operator, ...$values) + { + $this->predicates[] = [ + 'method' => 'orWhere', + 'field' => $field, + 'operator' => $operator, + 'values' => $values, + ]; + return $this; + } + + public function group(string $logic, ClauseBuilder ...$clauses) + { + foreach ($clauses as $builder) { + $this->groups[] = ['method' => 'group', 'logic' => $logic, 'builder' => $builder]; + } + return $this; + } + + public function andGroup(string $logic, ClauseBuilder ...$clauses) + { + foreach ($clauses as $builder) { + $this->groups[] = ['method' => 'andGroup', 'logic' => $logic, 'builder' => $builder]; + } + return $this; + } + + public function orGroup(string $logic, ClauseBuilder ...$clauses) + { + foreach ($clauses as $builder) { + $this->groups[] = ['method' => 'orGroup', 'logic' => $logic, 'builder' => $builder]; + } + return $this; + } + + public function build(): string + { + return 'identity conditions'; + } + + public function reset() + { + ++$this->resetCalls; + $this->predicates = []; + $this->groups = []; + return $this; + } +}